mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-10 13:21:52 +00:00
Compare commits
1 Commits
MSA-1993-1
...
MSA-1900-9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2640a9ac87 |
@@ -1,171 +0,0 @@
|
||||
/**
|
||||
* Utilitech Glass Break Sensor
|
||||
*
|
||||
* Author: Adam Heinmiller
|
||||
*
|
||||
* Date: 2014-11-09
|
||||
*/
|
||||
|
||||
metadata
|
||||
{
|
||||
definition (namespace: "adamheinmiller", name: "Utilitech Glass Break Sensor", author: "Adam Heinmiller")
|
||||
{
|
||||
capability "Contact Sensor"
|
||||
capability "Battery"
|
||||
|
||||
fingerprint deviceId:"0xA102", inClusters:"0x20, 0x9C, 0x80, 0x82, 0x84, 0x87, 0x85, 0x72, 0x86, 0x5A"
|
||||
}
|
||||
|
||||
simulator
|
||||
{
|
||||
status "Activate Sensor": "command: 9C02, payload: 26 00 FF 00 00"
|
||||
status "Reset Sensor": "command: 9C02, payload: 26 00 00 00 00"
|
||||
|
||||
status "Battery Status 25%": "command: 8003, payload: 19"
|
||||
status "Battery Status 50%": "command: 8003, payload: 32"
|
||||
status "Battery Status 75%": "command: 8003, payload: 4B"
|
||||
status "Battery Status 100%": "command: 8003, payload: 64"
|
||||
}
|
||||
|
||||
tiles
|
||||
{
|
||||
standardTile("contact", "device.contact", width: 2, height: 2)
|
||||
{
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#FF0000"
|
||||
}
|
||||
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat")
|
||||
{
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main "contact"
|
||||
details(["contact", "battery"])
|
||||
}
|
||||
}
|
||||
|
||||
def installed()
|
||||
{
|
||||
|
||||
updated()
|
||||
}
|
||||
|
||||
def updated()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
def getTimestamp()
|
||||
{
|
||||
return new Date().time
|
||||
}
|
||||
|
||||
def getBatteryLevel(int pNewLevel)
|
||||
{
|
||||
def bl = state.BatteryLevel ?: [pNewLevel, pNewLevel, pNewLevel] as int[]
|
||||
|
||||
def iAvg = 4 + ((int)(pNewLevel + bl[0] + bl[1] + bl[2]) / 4)
|
||||
|
||||
state.BatteryLevel = [pNewLevel, bl[0], bl[1]]
|
||||
|
||||
//log.debug "New Bat Level: ${iAvg - (iAvg % 5)}, $state.BatteryLevel"
|
||||
|
||||
return iAvg - (iAvg % 5)
|
||||
}
|
||||
|
||||
|
||||
|
||||
def parse(String description)
|
||||
{
|
||||
def result = []
|
||||
|
||||
// "0x20, 0x9C, 0x80, 0x82, 0x84, 0x87, 0x85, 0x72, 0x86, 0x5A"
|
||||
|
||||
def cmd = zwave.parse(description)
|
||||
|
||||
|
||||
//log.debug "Parse: Desc: $description, CMD: $cmd"
|
||||
|
||||
if (cmd)
|
||||
{
|
||||
result << zwaveEvent(cmd)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
|
||||
{
|
||||
logCommand(cmd)
|
||||
|
||||
def result = []
|
||||
|
||||
|
||||
result << response(zwave.wakeUpV2.wakeUpNoMoreInformation())
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd)
|
||||
{
|
||||
logCommand(cmd)
|
||||
|
||||
def result = []
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd)
|
||||
{
|
||||
logCommand(cmd)
|
||||
|
||||
def result = []
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd)
|
||||
{
|
||||
logCommand(cmd)
|
||||
|
||||
def result = [name: "battery", unit: "%", value: getBatteryLevel(cmd.batteryLevel)]
|
||||
|
||||
return createEvent(result)
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd)
|
||||
{
|
||||
logCommand(cmd)
|
||||
|
||||
|
||||
def result = [name: "contact"]
|
||||
|
||||
if (cmd.sensorState == 0)
|
||||
{
|
||||
result += [value: "closed", descriptionText: "${device.displayName} has reset"]
|
||||
}
|
||||
else if (cmd.sensorState == 255)
|
||||
{
|
||||
result += [value: "open", descriptionText: "${device.displayName} detected broken glass"]
|
||||
}
|
||||
|
||||
|
||||
return createEvent(result)
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd)
|
||||
{
|
||||
logCommand("**Unhandled**: $cmd")
|
||||
|
||||
return createEvent([descriptionText: "Unhandled: ${device.displayName}: ${cmd}", displayed: false])
|
||||
}
|
||||
|
||||
|
||||
def logCommand(cmd)
|
||||
{
|
||||
log.debug "Device Command: $cmd"
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -1,44 +0,0 @@
|
||||
# Express Controls EZMultiPli
|
||||
|
||||
Works with:
|
||||
|
||||
* [Express Controls EZMultiPli](https://www.smartthings.com/works-with-smartthings/)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Release Notes](#release-notes)
|
||||
* [Capabilities](#capabilities)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Release Notes
|
||||
|
||||
* **2017-04-19** - _dkirker_ - Update default config values in config value range check functions, use lux if lum option is null, fix NullPointerException on initial pairing when color data has not been set (and set the default color data!)
|
||||
* **2017-04-10** - _DrZwave_ (with help from Donald Kirker) - changed fingerprint to the new format, lowered the OnTime and other parameters to be "more in line with ST user expectations", get the luminance in LUX so it reports in lux all the time.
|
||||
* **2016-10-06** - _erocm1231_ - Added "updated" method to run when configuration options are changed. Depending on model of unit, luminance is being reported as a relative percentace or as a lux value. Added the option to configure this in the handler.
|
||||
* **2016-01-28** - _erocm1231_ - Changed the configuration method to use scaledConfiguration so that it properly formatted negative numbers. Also, added configurationGet and a configurationReport method so that config values can be verified.
|
||||
* **2015-12-04** - _erocm1231_ - added range value to preferences as suggested by @Dela-Rick.
|
||||
* **2015-11-26** - _erocm1231_ - Fixed null condition error when adding as a new device.
|
||||
* **2015-11-24** - _erocm1231_ - Added refresh command. Made a few changes to how the handler maps colors to the LEDs. Fixed the device not having its on/off status updated when colors are changed.
|
||||
* **2015-11-23** - _erocm1231_ - Changed the look to match SmartThings v2 devices.
|
||||
* **2015-11-21** - _erocm1231_ - Made code much more efficient. Also made it compatible when setColor is passed a hex value. Mapping of special colors: Soft White - Default - Yellow, White - Concentrate - White, Daylight - Energize - Teal, Warm White - Relax - Yellow
|
||||
* **2015-11-19** - _erocm1231_ - Fixed a couple incorrect colors, changed setColor to be more compatible with other apps
|
||||
* **2015-11-18** - _erocm1231_ - Added to setColor for compatibility with Smart Lighting
|
||||
* **v0.1.0** - _DrZWave_ - chose better icons, Got color LED to work - first fully functional version
|
||||
* **v0.0.9** - _jrs_ - got the temp and luminance to work. Motion works. Debugging the color wheel.
|
||||
* **v0.0.8** - _DrZWave_ 2/25/2015 - change the color control to be tiles since there are only 8 colors.
|
||||
* **v0.0.7** - _jrs_ - 02/23/2015 - Jim Sulin
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Actuator** - represents that a Device has commands
|
||||
* **Sensor** - detects sensor events
|
||||
* **Motion Sensor** - can detect motion
|
||||
* **Temperature Measurement** - defines device measures current temperature
|
||||
* **Illuminance Measurement** - gives the illuminance reading from devices that support it
|
||||
* **Switch** - can detect state (possible values: on/off)
|
||||
* **Color Control** - represents that the color attributes of a device can be controlled (hue, saturation, color value
|
||||
* **Configuration** - configure() command called when device is installed or device preferences updated
|
||||
* **Refresh** - refresh() command for status updates
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -2,6 +2,27 @@
|
||||
// Motion Sensor - Temperature - Light level - 8 Color Indicator LED - Z-Wave Range Extender - Wall Powered
|
||||
// driver for SmartThings
|
||||
// The EZMultiPli is also known as the HSM200 from HomeSeer.com
|
||||
//
|
||||
// 2017-04-10 - DrZwave (with help from Don Kirker) - changed fingerprint to the new format, lowered the OnTime
|
||||
// and other parameters to be "more in line with ST user expectations", get the luminance in LUX so it reports in lux all the time.
|
||||
// 2016-10-06 - erocm1231 - Added "updated" method to run when configuration options are changed. Depending on model of unit, luminance is being
|
||||
// reported as a relative percentace or as a lux value. Added the option to configure this in the handler.
|
||||
// 2016-01-28 - erocm1231 - Changed the configuration method to use scaledConfiguration so that it properly formatted negative numbers.
|
||||
// Also, added configurationGet and a configurationReport method so that config values can be verified.
|
||||
// 2015-12-04 - erocm1231 - added range value to preferences as suggested by @Dela-Rick.
|
||||
// 2015-11-26 - erocm1231 - Fixed null condition error when adding as a new device.
|
||||
// 2015-11-24 - erocm1231 - Added refresh command. Made a few changes to how the handler maps colors to the LEDs. Fixed
|
||||
// the device not having its on/off status updated when colors are changed.
|
||||
// 2015-11-23 - erocm1231 - Changed the look to match SmartThings v2 devices.
|
||||
// 2015-11-21 - erocm1231 - Made code much more efficient. Also made it compatible when setColor is passed a hex value.
|
||||
// Mapping of special colors: Soft White - Default - Yellow, White - Concentrate - White,
|
||||
// Daylight - Energize - Teal, Warm White - Relax - Yellow
|
||||
// 2015-11-19 - erocm1231 - Fixed a couple incorrect colors, changed setColor to be more compatible with other apps
|
||||
// 2015-11-18 - erocm1231 - Added to setColor for compatibility with Smart Lighting
|
||||
// v0.1.0 - DrZWave - chose better icons, Got color LED to work - first fully functional version
|
||||
// v0.0.9 - jrs - got the temp and luminance to work. Motion works. Debugging the color wheel.
|
||||
// v0.0.8 - DrZWave 2/25/2015 - change the color control to be tiles since there are only 8 colors.
|
||||
// v0.0.7 - jrs - 02/23/2015 - Jim Sulin
|
||||
|
||||
metadata {
|
||||
definition (name: "EZmultiPli", namespace: "DrZWave", author: "Eric Ryherd", oauth: true) {
|
||||
@@ -38,9 +59,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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", 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:"#00A0DC"
|
||||
attributeState "turningOn", label:'${name}', icon:"st.switches.light.on", backgroundColor:"#79b821"
|
||||
attributeState "turningOff", label:'${name}', icon:"st.switches.light.off", backgroundColor:"#ffffff"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
@@ -52,20 +73,20 @@ metadata {
|
||||
}
|
||||
|
||||
standardTile("motion", "device.motion", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) {
|
||||
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#00A0DC"
|
||||
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#cccccc"
|
||||
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
|
||||
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
|
||||
}
|
||||
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: "#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
|
||||
[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
|
||||
]
|
||||
}
|
||||
|
||||
@@ -110,6 +131,7 @@ metadata {
|
||||
|
||||
} // end metadata
|
||||
|
||||
|
||||
// Parse incoming device messages from device to generate events
|
||||
def parse(String description){
|
||||
//log.debug "==> New Zwave Event: ${description}"
|
||||
@@ -123,7 +145,7 @@ def parse(String description){
|
||||
|
||||
def statusTextmsg = ""
|
||||
if (device.currentState('temperature') != null && device.currentState('illuminance') != null) {
|
||||
statusTextmsg = "${device.currentState('temperature').value} ° - ${device.currentState('illuminance').value} ${(lum == 1) ? "%" : "LUX"}"
|
||||
statusTextmsg = "${device.currentState('temperature').value} ° - ${device.currentState('illuminance').value} ${(lum == "" || lum == null || lum == 1) ? "%" : "LUX"}"
|
||||
sendEvent("name":"statusText", "value":statusTextmsg, displayed:false)
|
||||
}
|
||||
if (result != [null] && result != []) log.debug "Parse returned ${result}"
|
||||
@@ -146,7 +168,7 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelR
|
||||
break;
|
||||
case 0x03 : // SENSOR_TYPE_LUMINANCE_VERSION_1
|
||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
||||
if(lum == 1) map.unit = "%"
|
||||
if(lum == "" || lum == null || lum == 1) map.unit = "%"
|
||||
else map.unit = "lux"
|
||||
map.name = "illuminance"
|
||||
log.debug "Luminance report"
|
||||
@@ -181,8 +203,7 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
// The EZMultiPli sets the color back to #ffffff on "off" or at init, so update the ST device to reflect this.
|
||||
if (device.latestState("color") == null || (cmd.value == 0 && device.latestState("color").value != "#ffffff")) {
|
||||
if (cmd.value == 0 && device.latestState("color").value != "#ffffff") {
|
||||
sendEvent(name: "color", value: "#ffffff", displayed: true)
|
||||
}
|
||||
[name: "switch", value: cmd.value ? "on" : "off", type: "digital"]
|
||||
@@ -275,12 +296,12 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
// ensure we are passing acceptable param values for LiteMin & TempMin configs
|
||||
def checkLiteTempInput(value) {
|
||||
if (value == null) {
|
||||
value=6
|
||||
value=60
|
||||
}
|
||||
def liteTempVal = value.toInteger()
|
||||
switch (liteTempVal) {
|
||||
case { it < 0 }:
|
||||
return 6 // bad value, set to default
|
||||
return 60 // bad value, set to default
|
||||
break
|
||||
case { it > 127 }:
|
||||
return 127 // bad value, greater then MAX, set to MAX
|
||||
@@ -293,12 +314,12 @@ def checkLiteTempInput(value) {
|
||||
// ensure we are passing acceptable param value for OnTime config
|
||||
def checkOnTimeInput(value) {
|
||||
if (value == null) {
|
||||
value=2
|
||||
value=10
|
||||
}
|
||||
def onTimeVal = value.toInteger()
|
||||
switch (onTimeVal) {
|
||||
case { it < 0 }:
|
||||
return 2 // bad value set to default
|
||||
return 10 // bad value set to default
|
||||
break
|
||||
case { it > 127 }:
|
||||
return 127 // bad value, greater then MAX, set to MAX
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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:"#cccccc")
|
||||
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#00A0DC")
|
||||
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
||||
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
||||
}
|
||||
|
||||
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: "#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 "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 "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: "#00A0DC", nextState:"Sent"
|
||||
state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#79b821", 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: "#00A0DC", nextState:"Sent"
|
||||
state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#79b821", 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:"#00A0DC", nextState:"Sent"
|
||||
state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"Sent"
|
||||
state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,618 +0,0 @@
|
||||
/**
|
||||
* Centralite Keypad
|
||||
*
|
||||
* Copyright 2015-2016 Mitch Pond, Zack Cornelius
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Centralite Keypad", namespace: "mitchpond", author: "Mitch Pond") {
|
||||
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Motion Sensor"
|
||||
capability "Sensor"
|
||||
capability "Temperature Measurement"
|
||||
capability "Refresh"
|
||||
capability "Lock Codes"
|
||||
capability "Tamper Alert"
|
||||
capability "Tone"
|
||||
capability "button"
|
||||
capability "polling"
|
||||
capability "Contact Sensor"
|
||||
|
||||
attribute "armMode", "String"
|
||||
attribute "lastUpdate", "String"
|
||||
|
||||
command "setDisarmed"
|
||||
command "setArmedAway"
|
||||
command "setArmedStay"
|
||||
command "setArmedNight"
|
||||
command "setExitDelay", ['number']
|
||||
command "setEntryDelay", ['number']
|
||||
command "testCmd"
|
||||
command "sendInvalidKeycodeResponse"
|
||||
command "acknowledgeArmRequest"
|
||||
|
||||
fingerprint endpointId: "01", profileId: "0104", deviceId: "0401", inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019,0501", manufacturer: "CentraLite", model: "3400", deviceJoinName: "Xfinity 3400-X Keypad"
|
||||
fingerprint endpointId: "01", profileId: "0104", deviceId: "0401", inClusters: "0000,0001,0003,0020,0402,0500,0501,0B05,FC04", outClusters: "0019,0501", manufacturer: "CentraLite", model: "3405-L", deviceJoinName: "Iris 3405-L Keypad"
|
||||
}
|
||||
|
||||
preferences{
|
||||
input ("tempOffset", "number", title: "Enter an offset to adjust the reported temperature",
|
||||
defaultValue: 0, displayDuringSetup: false)
|
||||
input ("beepLength", "number", title: "Enter length of beep in seconds",
|
||||
defaultValue: 1, displayDuringSetup: false)
|
||||
|
||||
input ("motionTime", "number", title: "Time in seconds for Motion to become Inactive (Default:10, 0=disabled)", defaultValue: 10, displayDuringSetup: false)
|
||||
}
|
||||
|
||||
tiles (scale: 2) {
|
||||
multiAttributeTile(name: "keypad", type:"generic", width:6, height:4, canChangeIcon: true) {
|
||||
tileAttribute ("device.armMode", key: "PRIMARY_CONTROL") {
|
||||
attributeState("disarmed", label:'${currentValue}', icon:"st.Home.home2", backgroundColor:"#44b621")
|
||||
attributeState("armedStay", label:'ARMED/STAY', icon:"st.Home.home3", backgroundColor:"#ffa81e")
|
||||
attributeState("armedAway", label:'ARMED/AWAY', icon:"st.nest.nest-away", backgroundColor:"#d04e00")
|
||||
}
|
||||
tileAttribute("device.lastUpdate", key: "SECONDARY_CONTROL") {
|
||||
attributeState("default", label:'Updated: ${currentValue}')
|
||||
}
|
||||
/*
|
||||
tileAttribute("device.battery", key: "SECONDARY_CONTROL") {
|
||||
attributeState("default", label:'Battery: ${currentValue}%', unit:"%")
|
||||
}
|
||||
tileAttribute("device.battery", key: "VALUE_CONTROL") {
|
||||
attributeState "VALUE_UP", action: "refresh"
|
||||
attributeState "VALUE_DOWN", action: "refresh"
|
||||
}
|
||||
*/
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState "VALUE_UP", action: "refresh"
|
||||
attributeState "VALUE_DOWN", action: "refresh"
|
||||
}
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state "temperature", label: '${currentValue}°',
|
||||
backgroundColors:[
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
}
|
||||
|
||||
standardTile("motion", "device.motion", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
|
||||
state "active", label:'motion',icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
|
||||
state "inactive", label:'no motion',icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("tamper", "device.tamper", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
|
||||
state "clear", label: 'Tamper', icon:"st.motion.acceleration.inactive", backgroundColor: "#ffffff"
|
||||
state "detected", label: 'Tamper', icon:"st.motion.acceleration.active", backgroundColor:"#cc5c5c"
|
||||
}
|
||||
standardTile("Panic", "device.contact", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
|
||||
state "open", label: 'Panic', icon:"st.security.alarm.alarm", backgroundColor: "#ffffff"
|
||||
state "closed", label: 'Panic', icon:"st.security.alarm.clear", backgroundColor:"#bc2323"
|
||||
}
|
||||
|
||||
standardTile("Mode", "device.armMode", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
|
||||
state "disarmed", label:'OFF', icon:"st.Home.home2", backgroundColor:"#44b621"
|
||||
state "armedStay", label:'OFF', icon:"st.Home.home3", backgroundColor:"#ffffff"
|
||||
state "armedAway", label:'OFF', icon:"st.net.nest-away", backgroundColor:"#ffffff"
|
||||
}
|
||||
|
||||
standardTile("beep", "device.beep", decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff"
|
||||
}
|
||||
valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
valueTile("armMode", "device.armMode", decoration: "flat", width: 2, height: 2) {
|
||||
state "armMode", label: '${currentValue}'
|
||||
}
|
||||
|
||||
main (["keypad"])
|
||||
details (["keypad","motion","tamper","Panic","Mode","beep","refresh","battery"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'";
|
||||
def results = [];
|
||||
|
||||
//------Miscellaneous Zigbee message------//
|
||||
if (description?.startsWith('catchall:')) {
|
||||
|
||||
//log.debug zigbee.parse(description);
|
||||
|
||||
def message = zigbee.parse(description);
|
||||
|
||||
//------Profile-wide command (rattr responses, errors, etc.)------//
|
||||
if (message?.isClusterSpecific == false) {
|
||||
//------Default response------//
|
||||
if (message?.command == 0x0B) {
|
||||
if (message?.data[1] == 0x81)
|
||||
log.error "Device: unrecognized command: "+description;
|
||||
else if (message?.data[1] == 0x80)
|
||||
log.error "Device: malformed command: "+description;
|
||||
}
|
||||
//------Read attributes responses------//
|
||||
else if (message?.command == 0x01) {
|
||||
if (message?.clusterId == 0x0402) {
|
||||
log.debug "Device: read attribute response: "+description;
|
||||
|
||||
results = parseTempAttributeMsg(message)
|
||||
}}
|
||||
else
|
||||
log.debug "Unhandled profile-wide command: "+description;
|
||||
}
|
||||
//------Cluster specific commands------//
|
||||
else if (message?.isClusterSpecific) {
|
||||
//------IAS ACE------//
|
||||
if (message?.clusterId == 0x0501) {
|
||||
if (message?.command == 0x07) {
|
||||
motionON()
|
||||
}
|
||||
else if (message?.command == 0x04) {
|
||||
results = createEvent(name: "button", value: "pushed", data: [buttonNumber: 1], descriptionText: "$device.displayName panic button was pushed", isStateChange: true)
|
||||
panicContact()
|
||||
}
|
||||
else if (message?.command == 0x00) {
|
||||
results = handleArmRequest(message)
|
||||
log.trace results
|
||||
}
|
||||
}
|
||||
else log.debug "Unhandled cluster-specific command: "+description
|
||||
}
|
||||
}
|
||||
//------IAS Zone Enroll request------//
|
||||
else if (description?.startsWith('enroll request')) {
|
||||
log.debug "Sending IAS enroll response..."
|
||||
results = zigbee.enrollResponse()
|
||||
}
|
||||
//------Read Attribute response------//
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
results = parseReportAttributeMessage(description)
|
||||
}
|
||||
//------Temperature Report------//
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
log.debug "Got ST-style temperature report.."
|
||||
results = createEvent(getTemperatureResult(zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())))
|
||||
log.debug results
|
||||
}
|
||||
else if (description?.startsWith('zone status ')) {
|
||||
results = parseIasMessage(description)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
def configure() {
|
||||
log.debug "--- Configure Called"
|
||||
String hubZigbeeId = swapEndianHex(device.hub.zigbeeEui)
|
||||
def cmd = [
|
||||
//------IAS Zone/CIE setup------//
|
||||
"zcl global write 0x500 0x10 0xf0 {${hubZigbeeId}}", "delay 100",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200",
|
||||
|
||||
//------Set up binding------//
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x500 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x501 {${device.zigbeeId}} {}", "delay 200",
|
||||
|
||||
] +
|
||||
zigbee.configureReporting(1,0x20,0x20,3600,43200,0x01) +
|
||||
zigbee.configureReporting(0x0402,0x00,0x29,30,3600,0x0064)
|
||||
|
||||
return cmd + refresh()
|
||||
}
|
||||
|
||||
def poll() {
|
||||
refresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
return sendStatusToDevice() +
|
||||
zigbee.readAttribute(0x0001,0x20) +
|
||||
zigbee.readAttribute(0x0402,0x00)
|
||||
}
|
||||
|
||||
private formatLocalTime(time, format = "EEE, MMM d yyyy @ h:mm a z") {
|
||||
if (time instanceof Long) {
|
||||
time = new Date(time)
|
||||
}
|
||||
if (time instanceof String) {
|
||||
//get UTC time
|
||||
time = timeToday(time, location.timeZone)
|
||||
}
|
||||
if (!(time instanceof Date)) {
|
||||
return null
|
||||
}
|
||||
def formatter = new java.text.SimpleDateFormat(format)
|
||||
formatter.setTimeZone(location.timeZone)
|
||||
return formatter.format(time)
|
||||
}
|
||||
|
||||
private parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
//log.debug "Desc Map: $descMap"
|
||||
|
||||
def results = []
|
||||
|
||||
if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
log.debug "Received battery level report"
|
||||
results = createEvent(getBatteryResult(Integer.parseInt(descMap.value, 16)))
|
||||
}
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0034")
|
||||
{
|
||||
log.debug "Received Battery Rated Voltage: ${descMap.value}"
|
||||
}
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0036")
|
||||
{
|
||||
log.debug "Received Battery Alarm Voltage: ${descMap.value}"
|
||||
}
|
||||
else if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
results = createEvent(getTemperatureResult(value))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
private parseTempAttributeMsg(message) {
|
||||
byte[] temp = message.data[-2..-1].reverse()
|
||||
createEvent(getTemperatureResult(getTemperature(temp.encodeHex() as String)))
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getContactResult('closed')
|
||||
break
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getContactResult('open')
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
resultMap = getContactResult('closed')
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
resultMap = getContactResult('open')
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
case '0x0000':
|
||||
resultMap = createEvent(name: "tamper", value: "clear", isStateChange: true, displayed: false)
|
||||
break
|
||||
case '0x0004':
|
||||
resultMap = createEvent(name: "tamper", value: "detected", isStateChange: true, displayed: false)
|
||||
break;
|
||||
default:
|
||||
log.debug "Invalid message code in IAS message: ${msgCode}"
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map getMotionResult(value) {
|
||||
String linkText = getLinkText(device)
|
||||
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
|
||||
return [
|
||||
name: 'motion',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
def motionON() {
|
||||
log.debug "--- Motion Detected"
|
||||
sendEvent(name: "motion", value: "active", displayed:true, isStateChange: true)
|
||||
|
||||
//-- Calculate Inactive timeout value
|
||||
def motionTimeRun = (settings.motionTime?:0).toInteger()
|
||||
|
||||
//-- If Inactive timeout was configured
|
||||
if (motionTimeRun > 0) {
|
||||
log.debug "--- Will become inactive in $motionTimeRun seconds"
|
||||
runIn(motionTimeRun, "motionOFF")
|
||||
}
|
||||
}
|
||||
|
||||
def motionOFF() {
|
||||
log.debug "--- Motion Inactive (OFF)"
|
||||
sendEvent(name: "motion", value: "inactive", displayed:true, isStateChange: true)
|
||||
}
|
||||
|
||||
def panicContact() {
|
||||
log.debug "--- Panic button hit"
|
||||
sendEvent(name: "contact", value: "open", displayed: true, isStateChange: true)
|
||||
runIn(3, "panicContactClose")
|
||||
}
|
||||
|
||||
def panicContactClose()
|
||||
{
|
||||
sendEvent(name: "contact", value: "closed", displayed: true, isStateChange: true)
|
||||
}
|
||||
|
||||
//TODO: find actual good battery voltage range and update this method with proper values for min/max
|
||||
//
|
||||
//Converts the battery level response into a percentage to display in ST
|
||||
//and creates appropriate message for given level
|
||||
|
||||
private getBatteryResult(rawValue) {
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [name: 'battery']
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.5
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private getTemperature(value) {
|
||||
def celcius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celcius
|
||||
} else {
|
||||
return celsiusToFahrenheit(celcius) as Integer
|
||||
}
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
def linkText = getLinkText(device)
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
//------Command handlers------//
|
||||
private handleArmRequest(message){
|
||||
def keycode = new String(message.data[2..-2] as byte[],'UTF-8')
|
||||
def reqArmMode = message.data[0]
|
||||
//state.lastKeycode = keycode
|
||||
log.debug "Received arm command with keycode/armMode: ${keycode}/${reqArmMode}"
|
||||
|
||||
//Acknowledge the command. This may not be *technically* correct, but it works
|
||||
/*List cmds = [
|
||||
"raw 0x501 {09 01 00 0${reqArmMode}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
]
|
||||
def results = cmds?.collect { new physicalgraph.device.HubAction(it) } + createCodeEntryEvent(keycode, reqArmMode)
|
||||
*/
|
||||
def results = createCodeEntryEvent(keycode, reqArmMode)
|
||||
log.trace "Method: handleArmRequest(message): "+results
|
||||
return results
|
||||
}
|
||||
|
||||
def createCodeEntryEvent(keycode, armMode) {
|
||||
createEvent(name: "codeEntered", value: keycode as String, data: armMode as String,
|
||||
isStateChange: true, displayed: false)
|
||||
}
|
||||
|
||||
//
|
||||
//The keypad seems to be expecting responses that are not in-line with the HA 1.2 spec. Maybe HA 1.3 or Zigbee 3.0??
|
||||
//
|
||||
private sendStatusToDevice() {
|
||||
log.debug 'Sending status to device...'
|
||||
def armMode = device.currentValue("armMode")
|
||||
log.trace 'Arm mode: '+armMode
|
||||
def status = ''
|
||||
if (armMode == null || armMode == 'disarmed') status = 0
|
||||
else if (armMode == 'armedAway') status = 3
|
||||
else if (armMode == 'armedStay') status = 1
|
||||
else if (armMode == 'armedNight') status = 2
|
||||
|
||||
// If we're not in one of the 4 basic modes, don't update the status, don't want to override beep timings, exit delay is dependent on it being correct
|
||||
if (status != '')
|
||||
{
|
||||
return sendRawStatus(status)
|
||||
}
|
||||
else
|
||||
{
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Statuses:
|
||||
// 00 - Disarmed
|
||||
// 01 - Armed partial
|
||||
// 02 - Armed partial
|
||||
// 03 - Armed Away
|
||||
// 04 - ?
|
||||
// 05 - Fast beep (1 per second)
|
||||
// 05 - Entry delay (Uses seconds) Appears to keep the status lights as it was
|
||||
// 06 - Amber status blink (Ignores seconds)
|
||||
// 07 - ?
|
||||
// 08 - Red status blink
|
||||
// 09 - ?
|
||||
// 10 - Exit delay Slow beep (2 per second, accelerating to 1 beep per second for the last 10 seconds) - With red flashing status - Uses seconds
|
||||
// 11 - ?
|
||||
// 12 - ?
|
||||
// 13 - ?
|
||||
|
||||
private sendRawStatus(status, seconds = 00) {
|
||||
log.debug "Sending Status ${zigbee.convertToHexString(status)}${zigbee.convertToHexString(seconds)} to device..."
|
||||
|
||||
// Seems to require frame control 9, which indicates a "Server to client" cluster specific command (which seems backward? I thought the keypad was the server)
|
||||
List cmds = ["raw 0x501 {09 01 04 ${zigbee.convertToHexString(status)}${zigbee.convertToHexString(seconds)}}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", 'delay 100']
|
||||
|
||||
def results = cmds?.collect { new physicalgraph.device.HubAction(it) };
|
||||
return results
|
||||
}
|
||||
|
||||
def notifyPanelStatusChanged(status) {
|
||||
//TODO: not yet implemented. May not be needed.
|
||||
}
|
||||
//------------------------//
|
||||
|
||||
def setDisarmed() { setModeHelper("disarmed",0) }
|
||||
def setArmedAway(def delay=0) { setModeHelper("armedAway",delay) }
|
||||
def setArmedStay(def delay=0) { setModeHelper("armedStay",delay) }
|
||||
def setArmedNight(def delay=0) { setModeHelper("armedNight",delay) }
|
||||
|
||||
def setEntryDelay(delay) {
|
||||
setModeHelper("entryDelay", delay)
|
||||
sendRawStatus(5, delay) // Entry delay beeps
|
||||
}
|
||||
|
||||
def setExitDelay(delay) {
|
||||
setModeHelper("exitDelay", delay)
|
||||
sendRawStatus(10, delay) // Exit delay
|
||||
}
|
||||
|
||||
private setModeHelper(String armMode, delay) {
|
||||
sendEvent([name: "armMode", value: armMode, data: [delay: delay as int], isStateChange: true])
|
||||
def lastUpdate = formatLocalTime(now())
|
||||
sendEvent(name: "lastUpdate", value: lastUpdate, displayed: false)
|
||||
sendStatusToDevice()
|
||||
}
|
||||
|
||||
private setKeypadArmMode(armMode){
|
||||
Map mode = [disarmed: '00', armedAway: '03', armedStay: '01', armedNight: '02', entryDelay: '', exitDelay: '']
|
||||
if (mode[armMode] != '')
|
||||
{
|
||||
return ["raw 0x501 {09 01 04 ${mode[armMode]}00}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", 'delay 100']
|
||||
}
|
||||
}
|
||||
|
||||
def acknowledgeArmRequest(armMode){
|
||||
List cmds = [
|
||||
"raw 0x501 {09 01 00 0${armMode}}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 100"
|
||||
]
|
||||
def results = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
log.trace "Method: acknowledgeArmRequest(armMode): "+results
|
||||
return results
|
||||
}
|
||||
|
||||
def sendInvalidKeycodeResponse(){
|
||||
List cmds = [
|
||||
"raw 0x501 {09 01 00 04}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 100"
|
||||
]
|
||||
|
||||
log.trace 'Method: sendInvalidKeycodeResponse(): '+cmds
|
||||
return (cmds?.collect { new physicalgraph.device.HubAction(it) }) + sendStatusToDevice()
|
||||
}
|
||||
|
||||
def beep(def beepLength = settings.beepLength) {
|
||||
if ( beepLength == null )
|
||||
{
|
||||
beepLength = 0
|
||||
}
|
||||
def len = zigbee.convertToHexString(beepLength, 2)
|
||||
List cmds = ["raw 0x501 {09 01 04 05${len}}", 'delay 200',
|
||||
"send 0x${device.deviceNetworkId} 1 1", 'delay 500']
|
||||
cmds
|
||||
}
|
||||
|
||||
//------Utility methods------//
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
//------------------------//
|
||||
|
||||
private testCmd(){
|
||||
//log.trace zigbee.parse('catchall: 0104 0501 01 01 0140 00 4F2D 01 00 0000 07 00 ')
|
||||
//beep(10)
|
||||
//test exit delay
|
||||
//log.debug device.zigbeeId
|
||||
//testingTesting()
|
||||
//discoverCmds()
|
||||
//zigbee.configureReporting(1,0x20,0x20,3600,43200,0x01) //battery reporting
|
||||
//["raw 0x0001 {00 00 06 00 2000 20 100E FEFF 01}",
|
||||
//"send 0x${device.deviceNetworkId} 1 1"]
|
||||
//zigbee.command(0x0003, 0x00, "0500") //Identify: blinks connection light
|
||||
|
||||
//log.debug //temperature reporting
|
||||
|
||||
return zigbee.readAttribute(0x0020,0x01) +
|
||||
zigbee.readAttribute(0x0020,0x02) +
|
||||
zigbee.readAttribute(0x0020,0x03)
|
||||
}
|
||||
|
||||
private discoverCmds(){
|
||||
List cmds = ["raw 0x0501 {08 01 11 0011}", 'delay 200',
|
||||
"send 0x${device.deviceNetworkId} 1 1", 'delay 500']
|
||||
cmds
|
||||
}
|
||||
|
||||
private testingTesting() {
|
||||
log.debug "Delay: "+device.currentState("armMode").toString()
|
||||
List cmds = ["raw 0x501 {09 01 04 050A}", 'delay 200',
|
||||
"send 0x${device.deviceNetworkId} 1 1", 'delay 500']
|
||||
cmds
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -1,33 +0,0 @@
|
||||
# Osotech Plant Link
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [OSO Technologies PlantLink Soil Moisture Sensor](https://www.smartthings.com/works-with-smartthings/oso-technologies/oso-technologies-plantlink-soil-moisture-sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Sensor** - detects sensor events
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
Plant Link sensor is a ZigBee sleepy device and checks in every 15 minutes.
|
||||
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||
|
||||
* __32min__ checkInterval
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
|
||||
for the different models:
|
||||
* [OSO Technologies PlantLink Soil Moisture Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206868986-PlantLink-Soil-Moisture-Sensor)
|
||||
@@ -24,7 +24,6 @@ import groovy.json.JsonBuilder
|
||||
metadata {
|
||||
definition (name: "PlantLink", namespace: "OsoTech", author: "Oso Technologies") {
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
|
||||
command "setStatusIcon"
|
||||
command "setPlantFuelLevel"
|
||||
@@ -71,16 +70,6 @@ metadata {
|
||||
}
|
||||
}
|
||||
|
||||
def updated() {
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def setStatusIcon(value){
|
||||
def status = ''
|
||||
switch (value) {
|
||||
@@ -172,4 +161,4 @@ def parseDescriptionAsMap(description) {
|
||||
map += []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ metadata {
|
||||
}
|
||||
|
||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "illuminance", label:'${currentValue} lux', unit:""
|
||||
state "illuminance", label:'${currentValue} ${unit}', unit:"lux"
|
||||
}
|
||||
|
||||
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} lux', unit:""
|
||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
@@ -282,4 +282,5 @@ 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} lux', unit:""
|
||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
||||
}
|
||||
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()
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -1,50 +0,0 @@
|
||||
# Arrival Sensor HA (2016+ Model)
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [Samsung SmartThings Arrival Sensor](https://support.smartthings.com/hc/en-us/articles/212417083-Samsung-SmartThings-Arrival-Sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Battery](#battery)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Tone** - beep command to allow an audible tone
|
||||
* **Actuator** - device has commands
|
||||
* **Presence Sensor** - device tells presence with enum - {present, not present}
|
||||
* **Sensor** - device has attributes
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
|
||||
## Device Health
|
||||
|
||||
Arrival Sensor ZigBee is an untracked device. Sends broadcast of battery level every 20 seconds.
|
||||
Disconnects when Hub goes OFFLINE.
|
||||
|
||||
|
||||
## Battery
|
||||
|
||||
Uses 1 CR2032 Battery
|
||||
|
||||
* [Changing the Battery](https://support.smartthings.com/hc/en-us/articles/200907400-How-to-change-the-battery-in-the-SmartSense-Presence-Sensor-and-Samsung-SmartThings-Arrival-Sensor)
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the arrival sensor is out of range.
|
||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||
|
||||
* [Samsung SmartThings Arrival Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205382134-Samsung-SmartThings-Arrival-Sensor-2015-model-)
|
||||
|
||||
If the arrival sensor doesn't update its status, here are a few things you can try to debug.
|
||||
|
||||
* [Troubleshooting: Samsung SmartThings Arrival Sensor won't update its status](https://support.smartthings.com/hc/en-us/articles/200846514-Troubleshooting-Samsung-SmartThings-Arrival-Sensor-won-t-update-its-status)
|
||||
@@ -1,5 +1,3 @@
|
||||
import groovy.json.JsonOutput
|
||||
|
||||
/**
|
||||
* Copyright 2017 SmartThings
|
||||
*
|
||||
@@ -21,7 +19,6 @@ metadata {
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020", outClusters: "0003,0019",
|
||||
manufacturer: "SmartThings", model: "tagv4", deviceJoinName: "Arrival Sensor"
|
||||
@@ -61,11 +58,6 @@ def updated() {
|
||||
startTimer()
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Arrival sensors only goes OFFLINE when Hub is off
|
||||
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
def cmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + zigbee.batteryConfig(20, 20, 0x01)
|
||||
log.debug "configure -- cmds: ${cmds}"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -1,49 +0,0 @@
|
||||
# Arrival Sensor (2015 Model)
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [Arrival Sensor](https://www.smartthings.com/products/samsung-smartthings-arrival-sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Battery](#battery)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Tone** - beep command to allow an audible tone
|
||||
* **Actuator** - device has commands
|
||||
* **Signal Strength** - device can read the strength of signal- lqi: Link Quality Indication, rssi: Received Signal Strength Indication
|
||||
* **Presence Sensor** - device tells presence with enum - {present, not present}
|
||||
* **Sensor** - device has attributes
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
|
||||
## Device Health
|
||||
|
||||
Arrival Sensor ZigBee is an untracked device. Disconnects when Hub goes OFFLINE.
|
||||
|
||||
|
||||
## Battery
|
||||
|
||||
Uses 1 CR2032 Battery
|
||||
|
||||
* [Changing the Battery](https://support.smartthings.com/hc/en-us/articles/200907400-How-to-change-the-battery-in-the-SmartSense-Presence-Sensor-and-Samsung-SmartThings-Arrival-Sensor)
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the arrival sensor is out of range.
|
||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||
|
||||
* [Samsung SmartThings Arrival Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205382134-Samsung-SmartThings-Arrival-Sensor-2015-model-)
|
||||
|
||||
If the arrival sensor doesn't update its status, here are a few things you can try to debug.
|
||||
|
||||
* [Troubleshooting: Samsung SmartThings Arrival Sensor won't update its status](https://support.smartthings.com/hc/en-us/articles/200846514-Troubleshooting-Samsung-SmartThings-Arrival-Sensor-won-t-update-its-status)
|
||||
@@ -1,5 +1,3 @@
|
||||
import groovy.json.JsonOutput
|
||||
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
@@ -21,7 +19,6 @@ metadata {
|
||||
capability "Presence Sensor"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint profileId: "FC01", deviceId: "019A"
|
||||
fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000,0003", outClusters: "0003"
|
||||
@@ -114,11 +111,6 @@ def beep() {
|
||||
]
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Arrival sensors only goes OFFLINE when Hub is off
|
||||
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def results
|
||||
if (isBatteryMessage(description)) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Dimmer Switch", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.light") {
|
||||
definition (name: "Dimmer Switch", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.switch") {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Indicator"
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
|
||||
tiles {
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#e86d13"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
}
|
||||
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:"#00a0dc")
|
||||
state("tampered", label:'tampered', icon:"st.locks.lock.unlocked", backgroundColor:"#53a7c0")
|
||||
}
|
||||
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: "#e86d13"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
|
||||
@@ -21,7 +21,7 @@ Works with:
|
||||
|
||||
## Device Health
|
||||
|
||||
Plant Link sensor is a ZigBee sleepy device and checks in every 15 minutes.
|
||||
Plant Link sensor is a Z-wave sleepy device and checks in every 15 minutes.
|
||||
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||
|
||||
* __32min__ checkInterval
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -1,39 +0,0 @@
|
||||
# Smartalert Siren
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [FortrezZ Siren Strobe Alarm](https://www.smartthings.com/products/fortrezz-siren-strobe-alarm)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Actuator** - represents that a Device has commands
|
||||
* **Switch** - can detect state (possible values: on/off)
|
||||
* **Sensor** - detects sensor events
|
||||
* **Alarm** - allows for interacting with devices that serve as alarms
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
FortrezZ Siren Strobe Alarm is polled by the hub.
|
||||
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
|
||||
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
|
||||
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
|
||||
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
|
||||
|
||||
* __32min__ checkInterval
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||
Pairing needs to be tried again by placing the device closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||
* [FortrezZ Siren Strobe Alarm Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202294760-FortrezZ-Siren-Strobe-Alarm)
|
||||
@@ -21,12 +21,10 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Sensor"
|
||||
capability "Alarm"
|
||||
capability "Health Check"
|
||||
|
||||
command "test"
|
||||
|
||||
fingerprint deviceId: "0x1100", inClusters: "0x26,0x71"
|
||||
fingerprint mfr:"0084", prod:"0313", model:"010B", deviceJoinName: "FortrezZ Siren Strobe Alarm"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -70,16 +68,6 @@ metadata {
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def updated() {
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def on() {
|
||||
[
|
||||
zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||
@@ -161,10 +149,3 @@ def createEvents(physicalgraph.zwave.commands.basicv1.BasicReport cmd)
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
log.warn "UNEXPECTED COMMAND: $cmd"
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
secure(zwave.basicV1.basicGet())
|
||||
}
|
||||
@@ -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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", 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:"#e86d13")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
}
|
||||
standardTile("acceleration", "device.acceleration", decoration: "flat") {
|
||||
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#00A0DC")
|
||||
|
||||
@@ -33,6 +33,7 @@ metadata {
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3323-G", deviceJoinName: "Centralite Micro Door Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor"
|
||||
|
||||
attribute "status", "string"
|
||||
@@ -191,10 +192,6 @@ private List<Map> parseAxis(List<Map> attrData) {
|
||||
def y = hexToSignedInt(attrData.find { it.attrInt == 0x0013 }?.value)
|
||||
def z = hexToSignedInt(attrData.find { it.attrInt == 0x0014 }?.value)
|
||||
|
||||
if ([x, y ,z].any { it == null }) {
|
||||
return []
|
||||
}
|
||||
|
||||
def xyzResults = [:]
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
// This mapping matches the current behavior of the Device Handler for the Centralite sensors
|
||||
@@ -375,10 +372,6 @@ def updated() {
|
||||
}
|
||||
|
||||
private hexToSignedInt(hexVal) {
|
||||
if (!hexVal) {
|
||||
return null
|
||||
}
|
||||
|
||||
def unsignedVal = hexToInt(hexVal)
|
||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ metadata {
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3323-G", deviceJoinName: "Centralite Micro Door Sensor"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -22,16 +22,16 @@ metadata {
|
||||
|
||||
tiles {
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
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")
|
||||
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")
|
||||
}
|
||||
standardTile("freezerDoor", "device.contact", width: 2, height: 2, decoration: "flat") {
|
||||
state("closed", label:'Freezer', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||
state("open", label:'Freezer', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||
state("closed", label:'Freezer', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
state("open", label:'Freezer', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
}
|
||||
standardTile("mainDoor", "device.contact", width: 2, height: 2, decoration: "flat") {
|
||||
state("closed", label:'Fridge', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||
state("open", label:'Fridge', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||
state("closed", label:'Fridge', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
state("open", label:'Fridge', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
}
|
||||
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:"#00A0DC")
|
||||
state("open", label:'${name}', icon:"st.fridge.fridge-open", backgroundColor:"#e86d13")
|
||||
state("closed", label:'${name}', icon:"st.fridge.fridge-closed", backgroundColor:"#79b821")
|
||||
state("open", label:'${name}', icon:"st.fridge.fridge-open", backgroundColor:"#ffa81e")
|
||||
}
|
||||
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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'…', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", 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:"#cccccc"
|
||||
attributeState "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#ff0000"
|
||||
}
|
||||
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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", 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:"#cccccc"
|
||||
attributeState "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
|
||||
}
|
||||
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:"#00A0DC", nextState:"turningOff"
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", 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:"#cccccc"
|
||||
state "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||
|
||||
@@ -70,27 +70,19 @@ def parse(String description) {
|
||||
else {
|
||||
sendEvent(event)
|
||||
}
|
||||
} else {
|
||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||
if (descMap && descMap.clusterInt == 0x0006 && descMap.commandInt == 0x07) {
|
||||
if (descMap.data[0] == "00") {
|
||||
}
|
||||
else {
|
||||
def cluster = zigbee.parse(description)
|
||||
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
|
||||
if (cluster.data[0] == 0x00){
|
||||
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||
}
|
||||
} else if (device.getDataValue("manufacturer") == "sengled" && descMap && descMap.clusterInt == 0x0008 && descMap.attrInt == 0x0000) {
|
||||
// This is being done because the sengled element touch incorrectly uses the value 0xFF for the max level.
|
||||
// Per the ZCL spec for the UINT8 data type 0xFF is an invalid value, and 0xFE should be the max. Here we
|
||||
// manually handle the invalid attribute value since it will be ignored by getEvent as an invalid value.
|
||||
// We also set the level of the bulb to 0xFE so future level reports will be 0xFE until it is changed by
|
||||
// something else.
|
||||
if (descMap.value.toUpperCase() == "FF") {
|
||||
descMap.value = "FE"
|
||||
}
|
||||
sendHubCommand(zigbee.command(zigbee.LEVEL_CONTROL_CLUSTER, 0x00, "FE0000").collect { new physicalgraph.device.HubAction(it) }, 0)
|
||||
sendEvent(zigbee.getEventFromAttrData(descMap.clusterInt, descMap.attrInt, descMap.encoding, descMap.value))
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ metadata {
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart A19 Soft White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM 10 Year", deviceJoinName: "SYLVANIA Smart 10-Year A19"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY PAR38 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart PAR38 Soft White"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2017 SmartThings
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -64,7 +64,6 @@ private getATTRIBUTE_HUE() { 0x0000 }
|
||||
private getATTRIBUTE_SATURATION() { 0x0001 }
|
||||
private getHUE_COMMAND() { 0x00 }
|
||||
private getSATURATION_COMMAND() { 0x03 }
|
||||
private getMOVE_TO_HUE_AND_SATURATION_COMMAND() { 0x06 }
|
||||
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
@@ -85,11 +84,11 @@ def parse(String description) {
|
||||
|
||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
||||
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
|
||||
}
|
||||
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
|
||||
}
|
||||
}
|
||||
@@ -124,12 +123,7 @@ def ping() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() +
|
||||
zigbee.levelRefresh() +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) +
|
||||
zigbee.onOffConfig(0, 300) +
|
||||
zigbee.levelConfig()
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, DataType.UINT8, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, DataType.UINT8, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
@@ -139,38 +133,26 @@ def configure() {
|
||||
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
refresh()
|
||||
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, DataType.UINT8, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, DataType.UINT8, 1, 3600, 0x01) + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
private getScaledHue(value) {
|
||||
zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
}
|
||||
|
||||
private getScaledSaturation(value) {
|
||||
zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
log.trace "setColor($value)"
|
||||
zigbee.on() +
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_HUE_AND_SATURATION_COMMAND,
|
||||
getScaledHue(value.hue), getScaledSaturation(value.saturation), "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
zigbee.on() + setHue(value.hue) + "delay 500" + setSaturation(value.saturation)
|
||||
}
|
||||
|
||||
def setHue(value) {
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, getScaledHue(value), "00", "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE)
|
||||
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
|
||||
}
|
||||
|
||||
def setSaturation(value) {
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, getScaledSaturation(value), "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + "delay 1000" + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def installed() {
|
||||
@@ -179,4 +161,4 @@ def installed() {
|
||||
sendEvent(name: "level", value: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2017 SmartThings
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", 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") {
|
||||
@@ -78,7 +78,6 @@ private getATTRIBUTE_HUE() { 0x0000 }
|
||||
private getATTRIBUTE_SATURATION() { 0x0001 }
|
||||
private getHUE_COMMAND() { 0x00 }
|
||||
private getSATURATION_COMMAND() { 0x03 }
|
||||
private getMOVE_TO_HUE_AND_SATURATION_COMMAND() { 0x06 }
|
||||
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
|
||||
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
|
||||
|
||||
@@ -103,11 +102,11 @@ def parse(String description) {
|
||||
|
||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
||||
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
|
||||
}
|
||||
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
|
||||
}
|
||||
}
|
||||
@@ -142,13 +141,7 @@ def ping() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() +
|
||||
zigbee.levelRefresh() +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) +
|
||||
zigbee.onOffConfig(0, 300) +
|
||||
zigbee.levelConfig()
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, DataType.UINT8, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, DataType.UINT8, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
@@ -163,12 +156,7 @@ def configure() {
|
||||
|
||||
def setColorTemperature(value) {
|
||||
setGenericName(value)
|
||||
value = value as Integer
|
||||
def tempInMired = (1000000 / value) as Integer
|
||||
def finalHex = zigbee.swapEndianHex(zigbee.convertToHexString(tempInMired, 4))
|
||||
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, 0x0A, "$finalHex 0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE)
|
||||
zigbee.setColorTemperature(value)
|
||||
}
|
||||
|
||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
||||
@@ -192,31 +180,19 @@ def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
private getScaledHue(value) {
|
||||
zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
}
|
||||
|
||||
private getScaledSaturation(value) {
|
||||
zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
log.trace "setColor($value)"
|
||||
zigbee.on() +
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_HUE_AND_SATURATION_COMMAND,
|
||||
getScaledHue(value.hue), getScaledSaturation(value.saturation), "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
zigbee.on() + setHue(value.hue) + "delay 300" + setSaturation(value.saturation)
|
||||
}
|
||||
|
||||
def setHue(value) {
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, getScaledHue(value), "00", "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE)
|
||||
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
|
||||
}
|
||||
|
||||
def setSaturation(value) {
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, getScaledSaturation(value), "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + "delay 1000" + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def installed() {
|
||||
@@ -225,4 +201,4 @@ def installed() {
|
||||
sendEvent(name: "level", value: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2017 SmartThings
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", 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,11 +71,6 @@ 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"
|
||||
@@ -128,11 +123,7 @@ def ping() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() +
|
||||
zigbee.levelRefresh() +
|
||||
zigbee.colorTemperatureRefresh() +
|
||||
zigbee.onOffConfig(0, 300) +
|
||||
zigbee.levelConfig()
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
@@ -147,12 +138,7 @@ def configure() {
|
||||
|
||||
def setColorTemperature(value) {
|
||||
setGenericName(value)
|
||||
value = value as Integer
|
||||
def tempInMired = (1000000 / value) as Integer
|
||||
def finalHex = zigbee.swapEndianHex(zigbee.convertToHexString(tempInMired, 4))
|
||||
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_COLOR_TEMPERATURE_COMMAND, "$finalHex 0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE)
|
||||
zigbee.setColorTemperature(value)
|
||||
}
|
||||
|
||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
||||
|
||||
@@ -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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", 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:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", 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,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2017 SmartThings
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -55,7 +55,6 @@ private getATTRIBUTE_HUE() { 0x0000 }
|
||||
private getATTRIBUTE_SATURATION() { 0x0001 }
|
||||
private getHUE_COMMAND() { 0x00 }
|
||||
private getSATURATION_COMMAND() { 0x03 }
|
||||
private getMOVE_TO_HUE_AND_SATURATION_COMMAND() { 0x06 }
|
||||
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
@@ -73,11 +72,11 @@ def parse(String description) {
|
||||
|
||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
||||
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
|
||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
||||
}
|
||||
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "saturation", value: saturationValue, displayed:false)
|
||||
}
|
||||
}
|
||||
@@ -109,46 +108,28 @@ def configure() {
|
||||
}
|
||||
|
||||
def configureAttributes() {
|
||||
zigbee.onOffConfig() +
|
||||
zigbee.levelConfig()
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, DataType.UINT8, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, DataType.UINT8, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def refreshAttributes() {
|
||||
zigbee.onOffRefresh() +
|
||||
zigbee.levelRefresh() +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
}
|
||||
|
||||
private getScaledHue(value) {
|
||||
zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
}
|
||||
|
||||
private getScaledSaturation(value) {
|
||||
zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
log.trace "setColor($value)"
|
||||
zigbee.on() +
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_HUE_AND_SATURATION_COMMAND,
|
||||
getScaledHue(value.hue), getScaledSaturation(value.saturation), "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
zigbee.on() + setHue(value.hue) + ["delay 300"] + setSaturation(value.saturation) + ["delay 2000"] + refreshAttributes()
|
||||
}
|
||||
|
||||
def setHue(value) {
|
||||
//payload-> hue value, direction (00-> shortest distance), transition time (1/10th second)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, getScaledHue(value), "00", "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE)
|
||||
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
|
||||
}
|
||||
|
||||
def setSaturation(value) {
|
||||
//payload-> sat value, transition time
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, getScaledSaturation(value), "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) //payload-> sat value, transition time
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2017 SmartThings
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -70,7 +70,6 @@ private getATTRIBUTE_HUE() { 0x0000 }
|
||||
private getATTRIBUTE_SATURATION() { 0x0001 }
|
||||
private getHUE_COMMAND() { 0x00 }
|
||||
private getSATURATION_COMMAND() { 0x03 }
|
||||
private getMOVE_TO_HUE_AND_SATURATION_COMMAND() { 0x06 }
|
||||
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
|
||||
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
|
||||
|
||||
@@ -89,11 +88,11 @@ def parse(String description) {
|
||||
|
||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
||||
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
|
||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
||||
}
|
||||
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "saturation", value: saturationValue, displayed:false)
|
||||
}
|
||||
}
|
||||
@@ -125,16 +124,11 @@ def configure() {
|
||||
}
|
||||
|
||||
def configureAttributes() {
|
||||
zigbee.onOffConfig() +
|
||||
zigbee.levelConfig()
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, DataType.UINT8, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, DataType.UINT8, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def refreshAttributes() {
|
||||
zigbee.onOffRefresh() +
|
||||
zigbee.levelRefresh() +
|
||||
zigbee.colorTemperatureRefresh() +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
@@ -145,32 +139,17 @@ def setLevel(value) {
|
||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
}
|
||||
|
||||
private getScaledHue(value) {
|
||||
zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
}
|
||||
|
||||
private getScaledSaturation(value) {
|
||||
zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
log.trace "setColor($value)"
|
||||
zigbee.on() +
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_HUE_AND_SATURATION_COMMAND,
|
||||
getScaledHue(value.hue), getScaledSaturation(value.saturation), "0000") +
|
||||
zigbee.onOffRefresh() +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
zigbee.on() + setHue(value.hue) + ["delay 300"] + setSaturation(value.saturation) + ["delay 2000"] + refreshAttributes()
|
||||
}
|
||||
|
||||
def setHue(value) {
|
||||
//payload-> hue value, direction (00-> shortest distance), transition time (1/10th second)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, getScaledHue(value), "00", "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE)
|
||||
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
|
||||
}
|
||||
|
||||
def setSaturation(value) {
|
||||
//payload-> sat value, transition time
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, getScaledSaturation(value), "0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) //payload-> sat value, transition time
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2017 SmartThings
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -66,11 +66,6 @@ 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"
|
||||
@@ -100,14 +95,14 @@ def setLevel(value) {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||
|
||||
// Do NOT config if the device is the Eaton Halo_LT01, it responds with "switch:off" to onOffConfig, and maybe other weird things with the others
|
||||
// Do NOT config if the device is the Eaton Halo_LT01, it responds with "switch:off" to onOffConfig, and maybe other weird things with the others
|
||||
if (!((device.getDataValue("manufacturer") == "Eaton") && (device.getDataValue("model") == "Halo_LT01"))) {
|
||||
cmds += zigbee.onOffConfig() + zigbee.levelConfig()
|
||||
cmds = cmds + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
|
||||
}
|
||||
|
||||
cmds
|
||||
cmds
|
||||
}
|
||||
|
||||
def poll() {
|
||||
@@ -143,7 +138,7 @@ def configure() {
|
||||
log.debug "configure()"
|
||||
configureHealthCheck()
|
||||
// Implementation note: for the Eaton Halo_LT01, it responds with "switch:off" to onOffConfig, so be sure this is before the call to onOffRefresh
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
@@ -153,12 +148,7 @@ def updated() {
|
||||
|
||||
def setColorTemperature(value) {
|
||||
setGenericName(value)
|
||||
value = value as Integer
|
||||
def tempInMired = (1000000 / value) as Integer
|
||||
def finalHex = zigbee.swapEndianHex(zigbee.convertToHexString(tempInMired, 4))
|
||||
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_COLOR_TEMPERATURE_COMMAND, "$finalHex 0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE)
|
||||
zigbee.setColorTemperature(value) + ["delay 1500"] + zigbee.colorTemperatureRefresh()
|
||||
}
|
||||
|
||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2017 SmartThings
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -68,11 +68,6 @@ 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"
|
||||
@@ -99,11 +94,7 @@ def setLevel(value) {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() +
|
||||
zigbee.levelRefresh() +
|
||||
zigbee.colorTemperatureRefresh() +
|
||||
zigbee.onOffConfig() +
|
||||
zigbee.levelConfig()
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
|
||||
}
|
||||
|
||||
def poll() {
|
||||
@@ -138,7 +129,8 @@ def configureHealthCheck() {
|
||||
def configure() {
|
||||
log.debug "configure()"
|
||||
configureHealthCheck()
|
||||
refresh()
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||
|
||||
}
|
||||
|
||||
def updated() {
|
||||
@@ -148,12 +140,7 @@ def updated() {
|
||||
|
||||
def setColorTemperature(value) {
|
||||
setGenericName(value)
|
||||
value = value as Integer
|
||||
def tempInMired = (1000000 / value) as Integer
|
||||
def finalHex = zigbee.swapEndianHex(zigbee.convertToHexString(tempInMired, 4))
|
||||
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_COLOR_TEMPERATURE_COMMAND, "$finalHex 0000") +
|
||||
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE)
|
||||
zigbee.setColorTemperature(value) + ["delay 1500"] + zigbee.colorTemperatureRefresh()
|
||||
}
|
||||
|
||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Z-Wave Lock
|
||||
# Z-Wave Switch
|
||||
|
||||
Cloud Execution
|
||||
|
||||
@@ -6,6 +6,7 @@ Works with:
|
||||
|
||||
* [Yale Key Free Touchscreen Deadbolt (YRD240)](https://www.smartthings.com/works-with-smartthings/yale/yale-key-free-touchscreen-deadbolt-yrd240)
|
||||
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
@@ -40,3 +41,5 @@ If the device doesn't pair when trying from the SmartThings mobile app, it is po
|
||||
Pairing needs to be tried again by placing the device closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||
* [General Z-Wave/ZigBee Yale Lock Troubleshooting](https://support.smartthings.com/hc/en-us/articles/205138400-How-to-connect-Yale-locks)
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Z-Wave Metering Dimmer", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.light") {
|
||||
definition (name: "Z-Wave Metering Dimmer", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.switch") {
|
||||
capability "Switch"
|
||||
capability "Polling"
|
||||
capability "Power Meter"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -1,38 +0,0 @@
|
||||
# Z-Wave Water Valve
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [Leak Intelligence Leak Gopher Water Shutoff Valve](https://www.smartthings.com/works-with-smartthings/other/leak-intelligence-leak-gopher-water-shutoff-valve)
|
||||
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Troubleshooting](#Troubleshooting)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Actuator** - represents that a Device has commands
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
* **Valve** - allows for the control of a valve device
|
||||
* **Polling** - represents that poll() can be implemented for the device
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Sensor** - detects sensor events
|
||||
|
||||
## Device Health
|
||||
|
||||
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||
|
||||
* __32min__ checkInterval
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||
Pairing needs to be tried again by placing the device closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||
* [Leak Intelligence Leak Gopher Water Shutoff Valve Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/209631423-Leak-Gopher-Z-Wave-Valve-Control)
|
||||
|
||||
|
||||
@@ -14,14 +14,12 @@
|
||||
metadata {
|
||||
definition (name: "Z-Wave Water Valve", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Health Check"
|
||||
capability "Valve"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint deviceId: "0x1006", inClusters: "0x25"
|
||||
fingerprint mfr:"0173", prod:"0003", model:"0002", deviceJoinName: "Leak Intelligence Leak Gopher Water Shutoff Valve"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -55,14 +53,7 @@ metadata {
|
||||
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def updated() {
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
response(refresh())
|
||||
}
|
||||
|
||||
@@ -123,13 +114,6 @@ def poll() {
|
||||
zwave.switchBinaryV1.switchBinaryGet().format()
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
refresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh() is called"
|
||||
def commands = [zwave.switchBinaryV1.switchBinaryGet().format()]
|
||||
|
||||
711
smartapps/app-home-ai/app-home-ai.src/app-home-ai.groovy
Normal file
711
smartapps/app-home-ai/app-home-ai.src/app-home-ai.groovy
Normal file
@@ -0,0 +1,711 @@
|
||||
definition(
|
||||
name: "app.home.ai",
|
||||
namespace: "app.home.ai",
|
||||
author: "Eric Greer",
|
||||
description: "SmartThings SmartApp for app.home.ai.",
|
||||
category: "Fun & Social",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
|
||||
)
|
||||
|
||||
// These are preferences displayed in the smart phone app
|
||||
preferences {
|
||||
// we need a settings section to enable subscriptions
|
||||
section("Pick which devices home.ai will help you automate:"){
|
||||
input "motion", "capability.motionSensor", title: "Choose motion sensors", required: false, multiple: true
|
||||
input "contact", "capability.contactSensor", title: "Choose contact sensors", required: false, multiple: true
|
||||
input "lightswitch", "capability.switch", title: "Choose normal power switches", required: false, multiple: true
|
||||
input "lightswitchlevel", "capability.switchLevel", title: "Choose dimmer power switches", required: false, multiple: true
|
||||
input "presence", "capability.presenceSensor", title: "Choose presence sensors", required: false, multiple: true
|
||||
// removed in prod until dual-signal devices are supported
|
||||
//input "tempSensor", "capability.temperatureMeasurement", title: "Choose temperature sensors", required: false, multiple: true
|
||||
//input "humidity", "capability.relativeHumidityMeasurement", title: "Choose humidity sensors", required: false, multiple: true
|
||||
input "waterSensor", "capability.waterSensor", title: "Choose water sensors", required: false, multiple: true
|
||||
input "lock", "capability.lock", title: "Pick Door Locks", required: false, multiple: true
|
||||
input "garagedoor", "capability.garageDoorControl", title: "Pick garage doors", required: false, multiple: true
|
||||
input "touchsensor", "capability.touchSensor", title: "Pick touch sensors", required: false, multiple: true
|
||||
input "speechparser", "capability.speechRecognition", title: "Pick speech recognizers", required: false, multiple: true
|
||||
input "soundsensor", "capability.soundSensor", title: "Pick sound sensors", required: false, multiple: true
|
||||
input "smokedetector", "capability.smokeDetector", title: "Pick smoke detectors", required: false, multiple: true
|
||||
input "sleepsensor", "capability.sleepSensor", title: "Pick sleep sensors", required: false, multiple: true
|
||||
input "carbonsensor", "capability.carbonMonoxideDetector", title: "Pick carbon monoxide detectors", required: false, multiple: true
|
||||
input "button", "capability.button", title: "Pick buttons", required: false, multiple: true
|
||||
input "beacon", "capability.beacon", title: "Pick beacons", required: false, multiple: true
|
||||
input "alarm", "capability.alarm", title: "Pick alarms", required: false, multiple: true
|
||||
input "thermostat", "capability.thermostat", title: "Pick thermostats", required: false, multiple: true
|
||||
input "voltage", "capability.voltageMeasurement", title: "Pick voltage sensors", required: false, multiple: true
|
||||
input "windowshade", "capability.windowShade", title: "Pick window shades", required: false, multiple: true
|
||||
input "powermeter", "capability.powerMeter", title: "Pick power meters", required: false, multiple: true
|
||||
}
|
||||
}
|
||||
|
||||
// vlaues for security system are 'away', 'stay', or 'off'
|
||||
// off security
|
||||
def offSecurity() {
|
||||
sendLocationEvent(
|
||||
name: "alarmSystemStatus",
|
||||
value: "off",
|
||||
displayed: false,
|
||||
isStateChange: true)
|
||||
}
|
||||
|
||||
// stay security
|
||||
def staySecurity() {
|
||||
sendLocationEvent(
|
||||
name: "alarmSystemStatus",
|
||||
value: "stay",
|
||||
displayed: false,
|
||||
isStateChange: true)
|
||||
}
|
||||
|
||||
// away security
|
||||
def awaySecurity() {
|
||||
sendLocationEvent(
|
||||
name: "alarmSystemStatus",
|
||||
value: "away",
|
||||
displayed: false,
|
||||
isStateChange: true)
|
||||
}
|
||||
|
||||
// sets window shade open temperature
|
||||
def setWindowShadeOpen() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setWindowShadeOpen command recieved ${deviceID}")
|
||||
windowshade.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating window shade because it is the one specified: ${deviceID}");
|
||||
it.open()
|
||||
} else {
|
||||
log.debug("NOT operting window shade because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets window shade close temperature
|
||||
def setWindowShadeClosed() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setWindowShadeClosed command recieved ${deviceID}")
|
||||
windowshade.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating window shade because it is the one specified: ${deviceID}");
|
||||
it.close()
|
||||
} else {
|
||||
log.debug("NOT operting window shade because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat heating temperature
|
||||
def setThermostatHeatTemp() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatHeat command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setHeatingSetpoint(params.temp)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat cooling temperature
|
||||
def setThermostatCoolTemp() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatCool command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setCoolingSetpoint(params.temp)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat off
|
||||
def setThermostatOff() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatOff command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.off()
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat to heat
|
||||
def setThermostatHeat() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatHeat command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.heat()
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat to cool
|
||||
def setThermostatCool() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatCool command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.cool()
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat mode
|
||||
def setThermostatMode() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatMode command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setThermostatMode(params.mode)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat fan mode
|
||||
def setThermostatFanMode() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatFanMode command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setThermostatFanMode(params.mode)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sends an alarm strobe
|
||||
def strobeAlarm() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Alarm strobe command recieved ${deviceID}")
|
||||
alarm.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating alarm because it is the one specified: ${deviceID}");
|
||||
it.strobe()
|
||||
} else {
|
||||
log.debug("NOT operting alarm because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sends an alarm siren
|
||||
def sirenAlarm() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Alarm siren command recieved ${deviceID}")
|
||||
alarm.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating alarm because it is the one specified: ${deviceID}");
|
||||
it.siren()
|
||||
} else {
|
||||
log.debug("NOT operting alarm because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// disables an alarm siren
|
||||
def silenceAlarm() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Alarm silence command recieved ${deviceID}")
|
||||
alarm.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating alarm because it is the one specified: ${deviceID}");
|
||||
it.off()
|
||||
} else {
|
||||
log.debug("NOT operting alarm because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// opens a garage door
|
||||
def openGarage() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Open Garage command recieved ${deviceID}")
|
||||
garagedoor.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating garage door because it is the one specified: ${deviceID}");
|
||||
it.open()
|
||||
} else {
|
||||
log.debug("NOT operting garage door device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// closes a garage door
|
||||
def closeGarage() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Close Garage command recieved ${deviceID}")
|
||||
garagedoor.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating garage door because it is the one specified: ${deviceID}");
|
||||
it.close()
|
||||
} else {
|
||||
log.debug("NOT operting garage door device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// lock locks a door lock
|
||||
def lockDoor() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Lock command recieved ${deviceID}")
|
||||
lock.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating lock device because it is the one specified: ${deviceID}");
|
||||
it.lock()
|
||||
} else {
|
||||
log.debug("NOT operting lock device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unlock unlocks a door lock
|
||||
def unlockDoor() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Unlock command recieved ${deviceID}")
|
||||
lock.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating lock device because it is the one specified: ${deviceID}");
|
||||
it.unlock()
|
||||
} else {
|
||||
log.debug("NOT operting lock device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// turns on a wall switch as instructed from the homeai webservice
|
||||
def switchOn() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Switch on command recieved ${deviceID}")
|
||||
lightswitch.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating switch device because it is the one specified: ${deviceID}");
|
||||
it.on()
|
||||
} else {
|
||||
log.debug("Skipping switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// turns off a wall switch as instructed from the homeai webservice
|
||||
def switchOff() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Switch off desired for ${deviceID}")
|
||||
lightswitch.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating switch device because it is the one specified: ${deviceID}");
|
||||
it.off()
|
||||
} else {
|
||||
log.debug("Skipping switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the id of this smartthings hub
|
||||
def hubId() {
|
||||
log.debug("hub id requested.")
|
||||
def response = [hubId: location.hubs.id[0]]
|
||||
}
|
||||
|
||||
// This handles requests for device inventories
|
||||
def inventory() {
|
||||
def response = []
|
||||
|
||||
lightswitch.each {
|
||||
response << [name: it.displayName, value: it.currentValue("switch"), deviceId: it.id, type: "lightSwitch"]
|
||||
}
|
||||
|
||||
contact.each {
|
||||
response << [name: it.displayName, value: it.currentValue("contact"), deviceId: it.id, type: "contact"]
|
||||
}
|
||||
|
||||
|
||||
motion.each {
|
||||
response << [name: it.displayName, value: it.currentValue("motion"), deviceId: it.id, type: "motion"]
|
||||
}
|
||||
|
||||
presence.each {
|
||||
response << [name: it.displayName, value: it.currentValue("presence"), deviceId: it.id, type: "presence"]
|
||||
}
|
||||
|
||||
// removed until dual device functions are supported on the backend
|
||||
//tempSensor.each {
|
||||
// response << [name: it.displayName, value: it.currentValue("temperature"), deviceId: it.id, type: "tempSensor"]
|
||||
//}
|
||||
|
||||
//humidity.each {
|
||||
// response << [name: it.displayName, value: it.currentValue("humidity"), deviceId: it.id, type: "humiditySensor"]
|
||||
//}
|
||||
|
||||
|
||||
waterSensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("water"), deviceId: it.id, type: "waterSensor"]
|
||||
}
|
||||
|
||||
lock.each {
|
||||
response << [name: it.displayName, value: it.currentValue("lock"), deviceId: it.id, type: "lock"]
|
||||
}
|
||||
|
||||
|
||||
garagedoor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("door"), deviceId: it.id, type: "garagedoor"]
|
||||
}
|
||||
|
||||
|
||||
touchsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("touch"), deviceId: it.id, type: "touchsensor"]
|
||||
}
|
||||
|
||||
|
||||
speechparser.each {
|
||||
response << [name: it.displayName, value: it.currentValue("phraseSpoken"), deviceId: it.id, type: "speechparser"]
|
||||
}
|
||||
|
||||
|
||||
soundsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("sound"), deviceId: it.id, type: "sound"]
|
||||
}
|
||||
|
||||
|
||||
smokedetector.each {
|
||||
response << [name: it.displayName, value: it.currentValue("smoke"), deviceId: it.id, type: "smoke"]
|
||||
}
|
||||
|
||||
|
||||
sleepsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("sleeping"), deviceId: it.id, type: "sleepsensor"]
|
||||
}
|
||||
|
||||
|
||||
carbonsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("carbonMonoxide"), deviceId: it.id, type: "carbonsensor"]
|
||||
}
|
||||
|
||||
|
||||
button.each {
|
||||
response << [name: it.displayName, value: it.currentValue("button"), deviceId: it.id, type: "button"]
|
||||
}
|
||||
|
||||
|
||||
beacon.each {
|
||||
response << [name: it.displayName, value: it.currentValue("presence"), deviceId: it.id, type: "beacon"]
|
||||
}
|
||||
|
||||
|
||||
alarm.each {
|
||||
response << [name: it.displayName, value: it.currentValue("alarm"), deviceId: it.id, type: "alarm"]
|
||||
}
|
||||
|
||||
|
||||
thermostat.each {
|
||||
response << [name: it.displayName, value: it.currentValue("thermostatMode"), deviceId: it.id, type: "thermostat"]
|
||||
}
|
||||
|
||||
|
||||
voltage.each {
|
||||
response << [name: it.displayName, value: it.currentValue("voltage"), deviceId: it.id, type: "voltage"]
|
||||
}
|
||||
|
||||
|
||||
windowshade.each {
|
||||
response << [name: it.displayName, value: it.currentValue("windowShade"), deviceId: it.id, type: "windowshade"]
|
||||
}
|
||||
|
||||
|
||||
powermeter.each {
|
||||
response << [name: it.displayName, value: it.currentValue("power"), deviceId: it.id, type: "powermeter"]
|
||||
}
|
||||
|
||||
|
||||
lightswitchlevel.each {
|
||||
response << [name: it.displayName, value: it.currentValue("level"), deviceId: it.id, type: "lightswitchlevel"]
|
||||
}
|
||||
|
||||
|
||||
|
||||
log.debug("Inventory request processed. Response: " + response)
|
||||
return response
|
||||
}
|
||||
|
||||
// After the user hits the 'install' button in the mobile app
|
||||
def installed() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
// After app settings are changed. All subscriptions are wiped before this is invoked by smartthings.
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
// This appears to be what the tutorials meant to use in the examples
|
||||
def initialize() {
|
||||
|
||||
// SHM subscription
|
||||
// evt.value will be "off", "stay", or "away"
|
||||
subscribe(location, "alarmSystemStatus", eventForwarder)
|
||||
|
||||
|
||||
// motion sensor subscription
|
||||
subscribe(motion, "motion", eventForwarder)
|
||||
|
||||
|
||||
// Contact sensor subscription
|
||||
subscribe(contact, "contact", eventForwarder)
|
||||
|
||||
|
||||
// power plug subscription
|
||||
subscribe(lightswitch, "switch", eventForwarder)
|
||||
|
||||
|
||||
// presence sensor subscription
|
||||
subscribe(presence, "presence", eventForwarder)
|
||||
|
||||
|
||||
// temperature sensor subscription
|
||||
subscribe(tempSensor, "temperature", eventForwarder)
|
||||
|
||||
|
||||
// water sensor subscription
|
||||
subscribe(waterSensor, "water", eventForwarder)
|
||||
|
||||
|
||||
// humidity sensor subscription
|
||||
subscribe(humidity, "humidity", eventForwarder)
|
||||
|
||||
|
||||
// lock subscription
|
||||
subscribe(lock, "lock", eventForwarder)
|
||||
|
||||
|
||||
// garage door subscription
|
||||
subscribe(garagedoor, "garagedoor", eventForwarder)
|
||||
|
||||
|
||||
// touch sensor subscription
|
||||
subscribe(touchsensor, "touchsensor", eventForwarder)
|
||||
|
||||
|
||||
// speech parser subscription
|
||||
subscribe(speechparser, "phraseSpoken", eventForwarder)
|
||||
|
||||
|
||||
// sound sensor subscription
|
||||
subscribe(soundsensor, "sound", eventForwarder)
|
||||
|
||||
|
||||
// smoke detector subscription
|
||||
subscribe(smokedetector, "smoke", eventForwarder)
|
||||
|
||||
|
||||
// sleep sensor subscription
|
||||
subscribe(sleepsensor, "sleeping", eventForwarder)
|
||||
|
||||
|
||||
// carbon monoxide sensor subscription
|
||||
subscribe(carbonsensor, "carbonMonoxide", eventForwarder)
|
||||
|
||||
|
||||
// button subscription
|
||||
subscribe(button, "button", eventForwarder)
|
||||
|
||||
|
||||
// beacon subscription
|
||||
subscribe(beacon, "presence", eventForwarder)
|
||||
|
||||
|
||||
// alarm subscription
|
||||
subscribe(alarm, "alarm", eventForwarder)
|
||||
|
||||
// thermostat subscriptions
|
||||
subscribe(thermostat, "temperature", eventForwarder)
|
||||
subscribe(thermostat, "heatingSetpoint", eventForwarder)
|
||||
subscribe(thermostat, "coolingSetpoint", eventForwarder)
|
||||
subscribe(thermostat, "thermostatSetpoint", eventForwarder)
|
||||
subscribe(thermostat, "thermostatMode", eventForwarder)
|
||||
subscribe(thermostat, "thermostatFanMode", eventForwarder)
|
||||
subscribe(thermostat, "thermostatOperatingState", eventForwarder)
|
||||
|
||||
// voltage subscription
|
||||
subscribe(voltage, "voltage", eventForwarder)
|
||||
|
||||
// window shade subscription
|
||||
subscribe(windowshade, "windowShade", eventForwarder)
|
||||
|
||||
// shm events
|
||||
subscribe(location, "alarmSystemStatus", shmEventForwarder)
|
||||
|
||||
// power meter subscription
|
||||
subscribe(powermeter, "power", eventForwarder)
|
||||
|
||||
// level switch (dimmer switch)
|
||||
subscribe(lightswitchlevel, "level", eventForwarder)
|
||||
|
||||
}
|
||||
|
||||
def shmEventForwarder(evt) {
|
||||
// evt.value will be "off", "stay", or "away"
|
||||
log.debug("FORWARDING SHM CHANGE" + evt.value + " " + evt.hub.id)
|
||||
|
||||
def deviceState = evt.value
|
||||
def deviceId = "smarthomemonitor"
|
||||
def hubId = hubId()
|
||||
def params = [
|
||||
uri: "https://app.home.ai",
|
||||
path: "/smartThingsPostback/shmStateChange/${hubId}/${deviceId}/${deviceState}"
|
||||
]
|
||||
log.info(params)
|
||||
httpGet(params)
|
||||
}
|
||||
|
||||
// This is used to forward events to the home.ai webservice
|
||||
def eventForwarder(evt) {
|
||||
|
||||
def hubId = location.hubs.id[0]
|
||||
|
||||
log.debug(params.uri + " " + params.path)
|
||||
log.debug("FORWARDING EVENT" + evt.deviceId + " " + evt.value + " " + hubId)
|
||||
|
||||
def deviceId = evt.deviceId
|
||||
def deviceState = evt.value
|
||||
def params = [
|
||||
uri: "https://app.home.ai",
|
||||
path: "/smartThingsPostback/stateChange/${hubId}/${deviceId}/${deviceState}"
|
||||
]
|
||||
log.info(params)
|
||||
httpGet(params)
|
||||
}
|
||||
|
||||
// Mappings that serve web requests against our smart app
|
||||
mappings {
|
||||
path("/inventory") {
|
||||
action: [
|
||||
GET: "inventory"
|
||||
]
|
||||
}
|
||||
path("/hubId") {
|
||||
action: [
|
||||
GET: "hubId"
|
||||
]
|
||||
}
|
||||
path("/switchOn/:deviceID") {
|
||||
action: [
|
||||
GET: "switchOn"
|
||||
]
|
||||
}
|
||||
path("/switchOff/:deviceID") {
|
||||
action: [
|
||||
GET: "switchOff"
|
||||
]
|
||||
}
|
||||
path("/lock/:deviceID") {
|
||||
action: [
|
||||
GET: "lockDoor"
|
||||
]
|
||||
}
|
||||
path("/unlock/:deviceID") {
|
||||
action: [
|
||||
GET: "unlockDoor"
|
||||
]
|
||||
}
|
||||
path("/opengarage/:deviceID") {
|
||||
action: [
|
||||
GET: "openGarage"
|
||||
]
|
||||
}
|
||||
path("/closegarage/:deviceID") {
|
||||
action: [
|
||||
GET: "closeGarage"
|
||||
]
|
||||
}
|
||||
path("/strobeAlarm/:deviceID") {
|
||||
action: [
|
||||
GET: "strobeAlarm"
|
||||
]
|
||||
}
|
||||
path("/sirenAlarm/:deviceID") {
|
||||
action: [
|
||||
GET: "sirenAlarm"
|
||||
]
|
||||
}
|
||||
path("/silenceAlarm/:deviceID") {
|
||||
action: [
|
||||
GET: "silenceAlarm"
|
||||
]
|
||||
}
|
||||
path("/setThermostatHeatTemp/:deviceID/:temp") {
|
||||
action: [
|
||||
GET: "setThermostatHeatTemp"
|
||||
]
|
||||
}
|
||||
path("/setThermostatCoolTemp/:deviceID/:temp") {
|
||||
action: [
|
||||
GET: "setThermostatCoolTemp"
|
||||
]
|
||||
}
|
||||
path("/setThermostatHeat/:deviceID") {
|
||||
action: [
|
||||
GET: "setThermostatHeat"
|
||||
]
|
||||
}
|
||||
path("/setThermostatCool/:deviceID") {
|
||||
action: [
|
||||
GET: "setThermostatCool"
|
||||
]
|
||||
}
|
||||
path("/setThermostatMode/:deviceID/:mode") {
|
||||
action: [
|
||||
GET: "setThermostatMode"
|
||||
]
|
||||
}
|
||||
path("/setThermostatFanMode/:deviceID/:mode") {
|
||||
action: [
|
||||
GET: "setThermostatFanMode"
|
||||
]
|
||||
}
|
||||
path("/closeWindowShade/:deviceID") {
|
||||
action: [
|
||||
GET: "setWindowShadeClosed"
|
||||
]
|
||||
}
|
||||
path("/openWindowShade/:deviceID") {
|
||||
action: [
|
||||
GET: "setWindowShadeOpen"
|
||||
]
|
||||
}
|
||||
path("/awaySecurity") {
|
||||
action: [
|
||||
GET: "awaySecurity"
|
||||
]
|
||||
}
|
||||
path("/staySecurity") {
|
||||
action: [
|
||||
GET: "staySecurity"
|
||||
]
|
||||
}
|
||||
path("/offSecurity") {
|
||||
action: [
|
||||
GET: "offSecurity"
|
||||
]
|
||||
}
|
||||
}
|
||||
829
smartapps/demo-home-ai/demo-home-ai.src/demo-home-ai.groovy
Normal file
829
smartapps/demo-home-ai/demo-home-ai.src/demo-home-ai.groovy
Normal file
@@ -0,0 +1,829 @@
|
||||
definition(
|
||||
name: "demo.home.ai",
|
||||
namespace: "demo.home.ai",
|
||||
author: "Eric Greer",
|
||||
description: "SmartThings demo SmartApp for home.ai.",
|
||||
category: "Fun & Social",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
|
||||
)
|
||||
|
||||
// These are preferences displayed in the smart phone app
|
||||
preferences {
|
||||
// we need a settings section to enable subscriptions
|
||||
section("Pick which devices home.ai will help you automate:"){
|
||||
input "motion", "capability.motionSensor", title: "Choose motion sensors", required: false, multiple: true
|
||||
input "contact", "capability.contactSensor", title: "Choose contact sensors", required: false, multiple: true
|
||||
input "lightswitch", "capability.switch", title: "Choose normal power switches", required: false, multiple: true
|
||||
input "lightswitchlevel", "capability.switchLevel", title: "Choose dimmer power switches", required: false, multiple: true
|
||||
input "presence", "capability.presenceSensor", title: "Choose presence sensors", required: false, multiple: true
|
||||
input "tempSensor", "capability.temperatureMeasurement", title: "Choose temperature sensors", required: false, multiple: true
|
||||
input "humidity", "capability.relativeHumidityMeasurement", title: "Choose humidity sensors", required: false, multiple: true
|
||||
input "waterSensor", "capability.waterSensor", title: "Choose water sensors", required: false, multiple: true
|
||||
input "lock", "capability.lock", title: "Pick Door Locks", required: false, multiple: true
|
||||
input "garagedoor", "capability.garageDoorControl", title: "Pick garage doors", required: false, multiple: true
|
||||
input "touchsensor", "capability.touchSensor", title: "Pick touch sensors", required: false, multiple: true
|
||||
input "speechparser", "capability.speechRecognition", title: "Pick speech recognizers", required: false, multiple: true
|
||||
input "soundsensor", "capability.soundSensor", title: "Pick sound sensors", required: false, multiple: true
|
||||
input "smokedetector", "capability.smokeDetector", title: "Pick smoke detectors", required: false, multiple: true
|
||||
input "sleepsensor", "capability.sleepSensor", title: "Pick sleep sensors", required: false, multiple: true
|
||||
input "carbonsensor", "capability.carbonMonoxideDetector", title: "Pick carbon monoxide detectors", required: false, multiple: true
|
||||
input "button", "capability.button", title: "Pick buttons", required: false, multiple: true
|
||||
input "beacon", "capability.beacon", title: "Pick beacons", required: false, multiple: true
|
||||
input "alarm", "capability.alarm", title: "Pick alarms", required: false, multiple: true
|
||||
input "thermostat", "capability.thermostat", title: "Pick thermostats", required: false, multiple: true
|
||||
input "voltage", "capability.voltageMeasurement", title: "Pick voltage sensors", required: false, multiple: true
|
||||
input "windowshade", "capability.windowShade", title: "Pick window shades", required: false, multiple: true
|
||||
input "powermeter", "capability.powerMeter", title: "Pick power meters", required: false, multiple: true
|
||||
}
|
||||
}
|
||||
|
||||
// vlaues for security system are 'away', 'stay', or 'off'
|
||||
// off security
|
||||
def offSecurity() {
|
||||
sendLocationEvent(
|
||||
name: "alarmSystemStatus",
|
||||
value: "off",
|
||||
displayed: false,
|
||||
isStateChange: true)
|
||||
}
|
||||
|
||||
// stay security
|
||||
def staySecurity() {
|
||||
sendLocationEvent(
|
||||
name: "alarmSystemStatus",
|
||||
value: "stay",
|
||||
displayed: false,
|
||||
isStateChange: true)
|
||||
}
|
||||
|
||||
// away security
|
||||
def awaySecurity() {
|
||||
sendLocationEvent(
|
||||
name: "alarmSystemStatus",
|
||||
value: "away",
|
||||
displayed: false,
|
||||
isStateChange: true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// sets window shade open temperature
|
||||
def setWindowShadeOpen() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setWindowShadeOpen command recieved ${deviceID}")
|
||||
windowshade.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating window shade because it is the one specified: ${deviceID}");
|
||||
it.open()
|
||||
} else {
|
||||
log.debug("NOT operting window shade because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets window shade close temperature
|
||||
def setWindowShadeClosed() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setWindowShadeClosed command recieved ${deviceID}")
|
||||
windowshade.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating window shade because it is the one specified: ${deviceID}");
|
||||
it.close()
|
||||
} else {
|
||||
log.debug("NOT operting window shade because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// sets thermostat heating temperature
|
||||
def setThermostatHeatTemp() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatHeat command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setHeatingSetpoint(params.temp)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat cooling temperature
|
||||
def setThermostatCoolTemp() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatCool command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setCoolingSetpoint(params.temp)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat off
|
||||
def setThermostatOff() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatOff command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.off()
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sets thermostat to heat
|
||||
def setThermostatHeat() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatHeat command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.heat()
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sets thermostat to cool
|
||||
def setThermostatCool() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatCool command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.cool()
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sets thermostat mode
|
||||
def setThermostatMode() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatMode command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setThermostatMode(params.mode)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
// sets thermostat fan mode
|
||||
def setThermostatFanMode() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatFanMode command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setThermostatFanMode(params.mode)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// sends an alarm strobe
|
||||
def strobeAlarm() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Alarm strobe command recieved ${deviceID}")
|
||||
alarm.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating alarm because it is the one specified: ${deviceID}");
|
||||
it.strobe()
|
||||
} else {
|
||||
log.debug("NOT operting alarm because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sends an alarm siren
|
||||
def sirenAlarm() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Alarm siren command recieved ${deviceID}")
|
||||
alarm.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating alarm because it is the one specified: ${deviceID}");
|
||||
it.siren()
|
||||
} else {
|
||||
log.debug("NOT operting alarm because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// disables an alarm siren
|
||||
def silenceAlarm() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Alarm silence command recieved ${deviceID}")
|
||||
alarm.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating alarm because it is the one specified: ${deviceID}");
|
||||
it.off()
|
||||
} else {
|
||||
log.debug("NOT operting alarm because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// opens a garage door
|
||||
def openGarage() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Open Garage command recieved ${deviceID}")
|
||||
garagedoor.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating garage door because it is the one specified: ${deviceID}");
|
||||
it.open()
|
||||
} else {
|
||||
log.debug("NOT operting garage door device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// closes a garage door
|
||||
def closeGarage() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Close Garage command recieved ${deviceID}")
|
||||
garagedoor.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating garage door because it is the one specified: ${deviceID}");
|
||||
it.close()
|
||||
} else {
|
||||
log.debug("NOT operting garage door device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// lock locks a door lock
|
||||
def lockDoor() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Lock command recieved ${deviceID}")
|
||||
lock.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating lock device because it is the one specified: ${deviceID}");
|
||||
it.lock()
|
||||
} else {
|
||||
log.debug("NOT operting lock device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unlock unlocks a door lock
|
||||
def unlockDoor() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Unlock command recieved ${deviceID}")
|
||||
lock.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating lock device because it is the one specified: ${deviceID}");
|
||||
it.unlock()
|
||||
} else {
|
||||
log.debug("NOT operting lock device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// turns on a wall switch as instructed from the homeai webservice
|
||||
def switchOn() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Switch on command recieved ${deviceID}")
|
||||
lightswitch.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating switch device because it is the one specified: ${deviceID}");
|
||||
it.on()
|
||||
} else {
|
||||
log.debug("Skipping switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// turns off a wall switch as instructed from the homeai webservice
|
||||
def switchOff() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Switch off desired for ${deviceID}")
|
||||
lightswitch.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating switch device because it is the one specified: ${deviceID}");
|
||||
it.off()
|
||||
} else {
|
||||
log.debug("Skipping switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sets a level switch to off
|
||||
def setLevelSwitch0() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Level switch off desired for ${deviceID}")
|
||||
lightswitchlevel.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating level switch device because it is the one specified: ${deviceID}");
|
||||
it.setLevel(0)
|
||||
} else {
|
||||
log.debug("Skipping level switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sets a level switch to 25%
|
||||
def setLevelSwitch25() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Level switch 25 desired for ${deviceID}")
|
||||
lightswitchlevel.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating level switch device because it is the one specified: ${deviceID}");
|
||||
it.setLevel(25)
|
||||
} else {
|
||||
log.debug("Skipping level switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sets a level switch to 50%
|
||||
def setLevelSwitch50() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Level switch 50 desired for ${deviceID}")
|
||||
lightswitchlevel.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating level switch device because it is the one specified: ${deviceID}");
|
||||
it.setLevel(50)
|
||||
} else {
|
||||
log.debug("Skipping level switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sets a level switch to 75%
|
||||
def setLevelSwitch75() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Level switch 75 desired for ${deviceID}")
|
||||
lightswitchlevel.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating level switch device because it is the one specified: ${deviceID}");
|
||||
it.setLevel(75)
|
||||
} else {
|
||||
log.debug("Skipping level switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sets a level switch to 100%
|
||||
def setLevelSwitch100() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Level switch 100 desired for ${deviceID}")
|
||||
lightswitchlevel.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating level switch device because it is the one specified: ${deviceID}");
|
||||
it.setLevel(100)
|
||||
} else {
|
||||
log.debug("Skipping level switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// fetch the id of this smartthings hub
|
||||
def hubId() {
|
||||
log.debug("hub id requested.")
|
||||
def response = [hubId: location.hubs.id[0]]
|
||||
}
|
||||
|
||||
// This handles requests for device inventories
|
||||
def inventory() {
|
||||
def response = []
|
||||
|
||||
lightswitch.each {
|
||||
response << [name: it.displayName, value: it.currentValue("switch"), deviceId: it.id, type: "lightSwitch"]
|
||||
}
|
||||
|
||||
contact.each {
|
||||
response << [name: it.displayName, value: it.currentValue("contact"), deviceId: it.id, type: "contact"]
|
||||
}
|
||||
|
||||
|
||||
motion.each {
|
||||
response << [name: it.displayName, value: it.currentValue("motion"), deviceId: it.id, type: "motion"]
|
||||
}
|
||||
|
||||
|
||||
presence.each {
|
||||
response << [name: it.displayName, value: it.currentValue("presence"), deviceId: it.id, type: "presence"]
|
||||
}
|
||||
|
||||
|
||||
tempSensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("temperature"), deviceId: it.id, type: "tempSensor"]
|
||||
}
|
||||
|
||||
|
||||
waterSensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("water"), deviceId: it.id, type: "waterSensor"]
|
||||
}
|
||||
|
||||
|
||||
humidity.each {
|
||||
response << [name: it.displayName, value: it.currentValue("humidity"), deviceId: it.id, type: "humiditySensor"]
|
||||
}
|
||||
|
||||
|
||||
lock.each {
|
||||
response << [name: it.displayName, value: it.currentValue("lock"), deviceId: it.id, type: "lock"]
|
||||
}
|
||||
|
||||
|
||||
garagedoor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("door"), deviceId: it.id, type: "garagedoor"]
|
||||
}
|
||||
|
||||
|
||||
touchsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("touch"), deviceId: it.id, type: "touchsensor"]
|
||||
}
|
||||
|
||||
|
||||
speechparser.each {
|
||||
response << [name: it.displayName, value: it.currentValue("phraseSpoken"), deviceId: it.id, type: "speechparser"]
|
||||
}
|
||||
|
||||
|
||||
soundsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("sound"), deviceId: it.id, type: "sound"]
|
||||
}
|
||||
|
||||
|
||||
smokedetector.each {
|
||||
response << [name: it.displayName, value: it.currentValue("smoke"), deviceId: it.id, type: "smoke"]
|
||||
}
|
||||
|
||||
|
||||
sleepsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("sleeping"), deviceId: it.id, type: "sleepsensor"]
|
||||
}
|
||||
|
||||
|
||||
carbonsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("carbonMonoxide"), deviceId: it.id, type: "carbonsensor"]
|
||||
}
|
||||
|
||||
|
||||
button.each {
|
||||
response << [name: it.displayName, value: it.currentValue("button"), deviceId: it.id, type: "button"]
|
||||
}
|
||||
|
||||
|
||||
beacon.each {
|
||||
response << [name: it.displayName, value: it.currentValue("presence"), deviceId: it.id, type: "beacon"]
|
||||
}
|
||||
|
||||
|
||||
alarm.each {
|
||||
response << [name: it.displayName, value: it.currentValue("alarm"), deviceId: it.id, type: "alarm"]
|
||||
}
|
||||
|
||||
|
||||
thermostat.each {
|
||||
response << [name: it.displayName, value: it.currentValue("thermostatMode"), deviceId: it.id, type: "thermostat"]
|
||||
}
|
||||
|
||||
|
||||
voltage.each {
|
||||
response << [name: it.displayName, value: it.currentValue("voltage"), deviceId: it.id, type: "voltage"]
|
||||
}
|
||||
|
||||
|
||||
windowshade.each {
|
||||
response << [name: it.displayName, value: it.currentValue("windowShade"), deviceId: it.id, type: "windowshade"]
|
||||
}
|
||||
|
||||
|
||||
powermeter.each {
|
||||
response << [name: it.displayName, value: it.currentValue("power"), deviceId: it.id, type: "powermeter"]
|
||||
}
|
||||
|
||||
|
||||
lightswitchlevel.each {
|
||||
response << [name: it.displayName, value: it.currentValue("level"), deviceId: it.id, type: "lightswitchlevel"]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
log.debug("Inventory request processed. Response: " + response)
|
||||
return response
|
||||
}
|
||||
|
||||
// After the user hits the 'install' button in the mobile app
|
||||
def installed() {
|
||||
initialize()
|
||||
|
||||
|
||||
}
|
||||
|
||||
// After app settings are changed. All subscriptions are wiped before this is invoked by smartthings.
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
initialize()
|
||||
|
||||
}
|
||||
|
||||
// This appears to be what the tutorials meant to use in the examples
|
||||
def initialize() {
|
||||
|
||||
// motion sensor subscription
|
||||
subscribe(motion, "motion", eventForwarder)
|
||||
|
||||
|
||||
// Contact sensor subscription
|
||||
subscribe(contact, "contact", eventForwarder)
|
||||
|
||||
|
||||
// power plug subscription
|
||||
subscribe(lightswitch, "switch", eventForwarder)
|
||||
|
||||
|
||||
// presence sensor subscription
|
||||
subscribe(presence, "presence", eventForwarder)
|
||||
|
||||
|
||||
// temperature sensor subscription
|
||||
subscribe(tempSensor, "temperature", eventForwarder)
|
||||
|
||||
|
||||
// water sensor subscription
|
||||
subscribe(waterSensor, "water", eventForwarder)
|
||||
|
||||
|
||||
// humidity sensor subscription
|
||||
subscribe(humidity, "humidity", eventForwarder)
|
||||
|
||||
|
||||
// lock subscription
|
||||
subscribe(lock, "lock", eventForwarder)
|
||||
|
||||
|
||||
// garage door subscription
|
||||
subscribe(garagedoor, "garagedoor", eventForwarder)
|
||||
|
||||
|
||||
// touch sensor subscription
|
||||
subscribe(touchsensor, "touchsensor", eventForwarder)
|
||||
|
||||
|
||||
// speech parser subscription
|
||||
subscribe(speechparser, "phraseSpoken", eventForwarder)
|
||||
|
||||
|
||||
// sound sensor subscription
|
||||
subscribe(soundsensor, "sound", eventForwarder)
|
||||
|
||||
|
||||
// smoke detector subscription
|
||||
subscribe(smokedetector, "smoke", eventForwarder)
|
||||
|
||||
|
||||
// sleep sensor subscription
|
||||
subscribe(sleepsensor, "sleeping", eventForwarder)
|
||||
|
||||
|
||||
// carbon monoxide sensor subscription
|
||||
subscribe(carbonsensor, "carbonMonoxide", eventForwarder)
|
||||
|
||||
|
||||
// button subscription
|
||||
subscribe(button, "button", eventForwarder)
|
||||
|
||||
|
||||
// beacon subscription
|
||||
subscribe(beacon, "presence", eventForwarder)
|
||||
|
||||
|
||||
// alarm subscription
|
||||
subscribe(alarm, "alarm", eventForwarder)
|
||||
|
||||
// thermostat subscriptions
|
||||
subscribe(thermostat, "temperature", eventForwarder)
|
||||
subscribe(thermostat, "heatingSetpoint", eventForwarder)
|
||||
subscribe(thermostat, "coolingSetpoint", eventForwarder)
|
||||
subscribe(thermostat, "thermostatSetpoint", eventForwarder)
|
||||
subscribe(thermostat, "thermostatMode", eventForwarder)
|
||||
subscribe(thermostat, "thermostatFanMode", eventForwarder)
|
||||
subscribe(thermostat, "thermostatOperatingState", eventForwarder)
|
||||
|
||||
// voltage subscription
|
||||
subscribe(voltage, "voltage", eventForwarder)
|
||||
|
||||
// window shade subscription
|
||||
subscribe(windowshade, "windowShade", eventForwarder)
|
||||
|
||||
// shm events
|
||||
subscribe(location, "alarmSystemStatus", shmEventForwarder)
|
||||
|
||||
// power meter subscription
|
||||
subscribe(powermeter, "power", eventForwarder)
|
||||
|
||||
// level switch (dimmer switch)
|
||||
subscribe(lightswitchlevel, "level", eventForwarder)
|
||||
}
|
||||
|
||||
// This is used to forward events to the home.ai webservice
|
||||
def eventForwarder(evt) {
|
||||
|
||||
|
||||
log.debug(params.uri + " " + params.path)
|
||||
|
||||
log.debug("FORWARDING EVENT" + evt.deviceId + " " + evt.value + " " + evt.hub.id)
|
||||
|
||||
def deviceId = evt.deviceId
|
||||
def deviceState = evt.value
|
||||
def hubId = evt.hub.id
|
||||
def params = [
|
||||
uri: "https://demo.home.ai",
|
||||
path: "/smartThingsPostback/stateChange/${hubId}/${deviceId}/${deviceState}"
|
||||
]
|
||||
log.info(params)
|
||||
httpGet(params)
|
||||
}
|
||||
|
||||
// Mappings that serve web requests against our smart app
|
||||
mappings {
|
||||
path("/inventory") {
|
||||
action: [
|
||||
GET: "inventory"
|
||||
]
|
||||
}
|
||||
path("/hubId") {
|
||||
action: [
|
||||
GET: "hubId"
|
||||
]
|
||||
}
|
||||
path("/switchOn/:deviceID") {
|
||||
action: [
|
||||
GET: "switchOn"
|
||||
]
|
||||
}
|
||||
path("/switchOff/:deviceID") {
|
||||
action: [
|
||||
GET: "switchOff"
|
||||
]
|
||||
}
|
||||
path("/lock/:deviceID") {
|
||||
action: [
|
||||
GET: "lockDoor"
|
||||
]
|
||||
}
|
||||
path("/unlock/:deviceID") {
|
||||
action: [
|
||||
GET: "unlockDoor"
|
||||
]
|
||||
}
|
||||
path("/opengarage/:deviceID") {
|
||||
action: [
|
||||
GET: "openGarage"
|
||||
]
|
||||
}
|
||||
path("/closegarage/:deviceID") {
|
||||
action: [
|
||||
GET: "closeGarage"
|
||||
]
|
||||
}
|
||||
|
||||
path("/strobeAlarm/:deviceID") {
|
||||
action: [
|
||||
GET: "strobeAlarm"
|
||||
]
|
||||
}
|
||||
path("/sirenAlarm/:deviceID") {
|
||||
action: [
|
||||
GET: "sirenAlarm"
|
||||
]
|
||||
}
|
||||
path("/silenceAlarm/:deviceID") {
|
||||
action: [
|
||||
GET: "silenceAlarm"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
path("/setThermostatHeatTemp/:deviceID/:temp") {
|
||||
action: [
|
||||
GET: "setThermostatHeatTemp"
|
||||
]
|
||||
}
|
||||
path("/setThermostatCoolTemp/:deviceID/:temp") {
|
||||
action: [
|
||||
GET: "setThermostatCoolTemp"
|
||||
]
|
||||
}
|
||||
path("/setThermostatHeat/:deviceID") {
|
||||
action: [
|
||||
GET: "setThermostatHeat"
|
||||
]
|
||||
}
|
||||
path("/setThermostatCool/:deviceID") {
|
||||
action: [
|
||||
GET: "setThermostatCool"
|
||||
]
|
||||
}
|
||||
path("/setThermostatMode/:deviceID/:mode") {
|
||||
action: [
|
||||
GET: "setThermostatMode"
|
||||
]
|
||||
}
|
||||
path("/setThermostatFanMode/:deviceID/:mode") {
|
||||
action: [
|
||||
GET: "setThermostatFanMode"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
path("/closeWindowShade/:deviceID") {
|
||||
action: [
|
||||
GET: "setWindowShadeClosed"
|
||||
]
|
||||
}
|
||||
path("/openWindowShade/:deviceID") {
|
||||
action: [
|
||||
GET: "setWindowShadeOpen"
|
||||
]
|
||||
}
|
||||
|
||||
// level switch setting endpoints
|
||||
path("/levelSwitch0/:deviceID") {
|
||||
action: [
|
||||
GET: "setLevelSwitch0"
|
||||
]
|
||||
}
|
||||
path("/levelSwitch25/:deviceID") {
|
||||
action: [
|
||||
GET: "setLevelSwitch25"
|
||||
]
|
||||
}
|
||||
path("/levelSwitch50/:deviceID") {
|
||||
action: [
|
||||
GET: "setLevelSwitch50"
|
||||
]
|
||||
}
|
||||
path("/levelSwitch75/:deviceID") {
|
||||
action: [
|
||||
GET: "setLevelSwitch75"
|
||||
]
|
||||
}
|
||||
path("/levelSwitch100/:deviceID") {
|
||||
action: [
|
||||
GET: "setLevelSwitch100"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Smart home monitor controls
|
||||
path("/awaySecurity") {
|
||||
action: [
|
||||
GET: "awaySecurity"
|
||||
]
|
||||
}
|
||||
path("/staySecurity") {
|
||||
action: [
|
||||
GET: "staySecurity"
|
||||
]
|
||||
}
|
||||
path("/offSecurity") {
|
||||
action: [
|
||||
GET: "offSecurity"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -765,6 +765,7 @@ def turnOffSwitch() {
|
||||
} else {
|
||||
|
||||
device.off();
|
||||
|
||||
return [Device_id: params.id, result_action: "200"]
|
||||
}
|
||||
}
|
||||
@@ -788,7 +789,6 @@ def getTempSensorsStatus(id) {
|
||||
return []
|
||||
} else {
|
||||
def bat = getBatteryStatus(device.id)
|
||||
def scale = [Scale: location.temperatureScale]
|
||||
return [temperature: device.currentValue('temperature')] + bat + scale
|
||||
return [temperature: device.currentValue('temperature')] + bat
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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: true])
|
||||
}
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "inactive") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: false])
|
||||
}
|
||||
}
|
||||
|
||||
def presenceHandler(evt) {
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "not present") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: true])
|
||||
}
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "not present") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: false])
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
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 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,7 +1,3 @@
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
/**
|
||||
* OpenT2T SmartApp Test
|
||||
*
|
||||
@@ -43,7 +39,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, |
|
||||
@@ -59,7 +55,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
|
||||
@@ -70,49 +66,44 @@ 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"
|
||||
]
|
||||
}
|
||||
@@ -125,21 +116,14 @@ def installed() {
|
||||
|
||||
def updated() {
|
||||
log.debug "Updating with settings: ${settings}"
|
||||
|
||||
//Initialize state variables if didn't exist.
|
||||
if (state.deviceSubscriptionMap == null) {
|
||||
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()
|
||||
}
|
||||
@@ -148,11 +132,9 @@ 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 ***/
|
||||
@@ -166,7 +148,7 @@ def registerAllDeviceSubscriptions() {
|
||||
def registerChangeHandler(myList) {
|
||||
myList.each { myDevice ->
|
||||
def theAtts = myDevice.supportedAttributes
|
||||
theAtts.each { att ->
|
||||
theAtts.each {att ->
|
||||
subscribe(myDevice, att.name, deviceEventHandler)
|
||||
log.info "Registering for ${myDevice.displayName}.${att.name}"
|
||||
}
|
||||
@@ -178,38 +160,31 @@ 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"]
|
||||
}
|
||||
|
||||
@@ -219,19 +194,18 @@ 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 {
|
||||
@@ -243,33 +217,25 @@ 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")
|
||||
@@ -281,17 +247,16 @@ 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) {
|
||||
@@ -299,40 +264,28 @@ 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 evtDevice = evt.device
|
||||
def evtDeviceType = getDeviceType(evtDevice)
|
||||
def deviceData = [];
|
||||
def evt_device = evt.device
|
||||
def evt_deviceType = getDeviceType(evt_device)
|
||||
def deviceInfo
|
||||
|
||||
if (evt.data != null) {
|
||||
def params = [ body: [deviceName: evt_device.displayName, deviceId: evt_device.id, locationId: location.id] ]
|
||||
|
||||
if(evt.data != null){
|
||||
def evtData = parseJson(evt.data)
|
||||
log.info "Received event for ${evtDevice.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
|
||||
log.info "Received event for ${evt_device.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 ${evtDevice.displayName} is ${state.deviceSubscriptionMap[evtDevice.id]}"
|
||||
state.deviceSubscriptionMap[evtDevice.id].each {
|
||||
log.debug "Current subscription urls for ${evt_device.displayName} is ${state.deviceSubscriptionMap[evt_device.id]}"
|
||||
state.deviceSubscriptionMap[evt_device.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}"
|
||||
@@ -345,27 +298,20 @@ 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 evtDevice = evt.device
|
||||
def evtDeviceType = getDeviceType(evtDevice)
|
||||
def params = [body: [eventType: evt.name, deviceId: evtDevice.id, locationId: location.id]]
|
||||
def evt_device = evt.device
|
||||
def evt_deviceType = getDeviceType(evt_device)
|
||||
log.info "DeviceName: ${evt_device.displayName}, DeviceID: ${evt_device.id}, deviceType: ${evt_deviceType}"
|
||||
|
||||
if (evt.name == "DeviceDeleted" && state.deviceSubscriptionMap[deviceId] != null) {
|
||||
state.deviceSubscriptionMap.remove(evtDevice.id)
|
||||
}
|
||||
def params = [ body: [ eventType:evt.name, deviceId: evt_device.id, locationId: location.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}"
|
||||
@@ -380,23 +326,6 @@ 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 ***/
|
||||
|
||||
@@ -405,10 +334,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)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,10 +350,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}"
|
||||
@@ -437,18 +366,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)
|
||||
@@ -462,7 +391,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)) {
|
||||
@@ -472,11 +401,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 {
|
||||
@@ -503,16 +432,17 @@ 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 {
|
||||
@@ -559,24 +489,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
|
||||
@@ -649,7 +579,8 @@ 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 = ""
|
||||
}
|
||||
@@ -658,5 +589,5 @@ private mapDeviceCommands(command, value) {
|
||||
break
|
||||
}
|
||||
|
||||
return [resultCommand, resultValue]
|
||||
return [resultCommand,resultValue]
|
||||
}
|
||||
|
||||
@@ -27,82 +27,84 @@ 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("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("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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 delay = (openThreshold != null && openThreshold != "") ? openThreshold * 60 : 600
|
||||
runIn(delay, doorOpenTooLong, [overwrite: true])
|
||||
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 doorClosed(evt) {
|
||||
log.trace "doorClosed($evt.name: $evt.value)"
|
||||
unschedule(doorOpenTooLong)
|
||||
def doorClosed(evt)
|
||||
{
|
||||
log.trace "doorClosed($evt.name: $evt.value)"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ def getCallbackUrl() { return "${getServerUrl()}/oauth/callback" }
|
||||
def apiURL(path = '/') { return "https://api.lifx.com/v1${path}" }
|
||||
def getSecretKey() { return appSettings.secretKey }
|
||||
def getClientId() { return appSettings.clientId }
|
||||
private getVendorName() { "LIFX" }
|
||||
|
||||
def authPage() {
|
||||
log.debug "authPage test1"
|
||||
@@ -77,7 +76,6 @@ def authPage() {
|
||||
return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) {
|
||||
section("Select your location") {
|
||||
input "selectedLocationId", "enum", required:true, title:"Select location ({{count}} found)", messageArgs: [count: count], multiple:false, options:options, submitOnChange: true
|
||||
paragraph "Devices will be added automatically from your ${vendorName} account. To add or delete devices please use the Official ${vendorName} App."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,20 +136,10 @@ def getDataForChild(child, startDate, endDate) {
|
||||
|
||||
def wattvisionURL = wattvisionURL(child.deviceNetworkId, startDate, endDate)
|
||||
if (wattvisionURL) {
|
||||
try {
|
||||
httpGet(uri: wattvisionURL) { response ->
|
||||
def json = new org.json.JSONObject(response.data.toString())
|
||||
child.addWattvisionData(json)
|
||||
return "success"
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException httpE) {
|
||||
log.error "Wattvision getDataForChild HttpResponseException: ${httpE} -> ${httpE.response.data}"
|
||||
//log.debug "wattvisionURL = ${wattvisionURL}"
|
||||
return "fail"
|
||||
} catch (e) {
|
||||
log.error "Wattvision getDataForChild General Exception: ${e}"
|
||||
//log.debug "wattvisionURL = ${wattvisionURL}"
|
||||
return "fail"
|
||||
httpGet(uri: wattvisionURL) { response ->
|
||||
def json = new org.json.JSONObject(response.data.toString())
|
||||
child.addWattvisionData(json)
|
||||
return "success"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,14 +164,9 @@ def wattvisionURL(senorId, startDate, endDate) {
|
||||
if (diff > 259200000) { // 3 days in milliseconds
|
||||
// Wattvision only allows pulling 3 hours of data at a time
|
||||
startDate = new Date(hours: endDate.hours - 3)
|
||||
} else if (diff < 10000) { // 10 seconds in milliseconds
|
||||
// Wattvision throws errors when the difference between start_time and end_time is 5 seconds or less
|
||||
// So we are going to make sure that we have a few more seconds of breathing room
|
||||
use (groovy.time.TimeCategory) {
|
||||
startDate = endDate - 10.seconds
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def params = [
|
||||
"sensor_id" : senorId,
|
||||
"api_id" : wattvisionApiAccess.id,
|
||||
@@ -495,3 +480,4 @@ def connectionSuccessful(deviceName, iconSrc) {
|
||||
|
||||
render contentType: 'text/html', data: html
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,713 @@
|
||||
definition(
|
||||
name: "stage.app.home.ai",
|
||||
namespace: "stage.app.home.ai",
|
||||
author: "Eric Greer",
|
||||
description: "SmartThings SmartApp for stage.app.home.ai.",
|
||||
category: "Fun & Social",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
|
||||
)
|
||||
|
||||
// These are preferences displayed in the smart phone app
|
||||
preferences {
|
||||
// we need a settings section to enable subscriptions
|
||||
section("Pick which devices home.ai will help you automate:"){
|
||||
input "motion", "capability.motionSensor", title: "Choose motion sensors", required: false, multiple: true
|
||||
input "contact", "capability.contactSensor", title: "Choose contact sensors", required: false, multiple: true
|
||||
input "lightswitch", "capability.switch", title: "Choose normal power switches", required: false, multiple: true
|
||||
input "lightswitchlevel", "capability.switchLevel", title: "Choose dimmer power switches", required: false, multiple: true
|
||||
input "presence", "capability.presenceSensor", title: "Choose presence sensors", required: false, multiple: true
|
||||
input "tempSensor", "capability.temperatureMeasurement", title: "Choose temperature sensors", required: false, multiple: true
|
||||
input "humidity", "capability.relativeHumidityMeasurement", title: "Choose humidity sensors", required: false, multiple: true
|
||||
input "waterSensor", "capability.waterSensor", title: "Choose water sensors", required: false, multiple: true
|
||||
input "lock", "capability.lock", title: "Pick Door Locks", required: false, multiple: true
|
||||
input "garagedoor", "capability.garageDoorControl", title: "Pick garage doors", required: false, multiple: true
|
||||
input "touchsensor", "capability.touchSensor", title: "Pick touch sensors", required: false, multiple: true
|
||||
input "speechparser", "capability.speechRecognition", title: "Pick speech recognizers", required: false, multiple: true
|
||||
input "soundsensor", "capability.soundSensor", title: "Pick sound sensors", required: false, multiple: true
|
||||
input "smokedetector", "capability.smokeDetector", title: "Pick smoke detectors", required: false, multiple: true
|
||||
input "sleepsensor", "capability.sleepSensor", title: "Pick sleep sensors", required: false, multiple: true
|
||||
input "carbonsensor", "capability.carbonMonoxideDetector", title: "Pick carbon monoxide detectors", required: false, multiple: true
|
||||
input "button", "capability.button", title: "Pick buttons", required: false, multiple: true
|
||||
input "beacon", "capability.beacon", title: "Pick beacons", required: false, multiple: true
|
||||
input "alarm", "capability.alarm", title: "Pick alarms", required: false, multiple: true
|
||||
input "thermostat", "capability.thermostat", title: "Pick thermostats", required: false, multiple: true
|
||||
input "voltage", "capability.voltageMeasurement", title: "Pick voltage sensors", required: false, multiple: true
|
||||
input "windowshade", "capability.windowShade", title: "Pick window shades", required: false, multiple: true
|
||||
input "powermeter", "capability.powerMeter", title: "Pick power meters", required: false, multiple: true
|
||||
}
|
||||
}
|
||||
|
||||
// vlaues for security system are 'away', 'stay', or 'off'
|
||||
// off security
|
||||
def offSecurity() {
|
||||
sendLocationEvent(
|
||||
name: "alarmSystemStatus",
|
||||
value: "off",
|
||||
displayed: false,
|
||||
isStateChange: true
|
||||
)
|
||||
}
|
||||
|
||||
// stay security
|
||||
def staySecurity() {
|
||||
sendLocationEvent(
|
||||
name: "alarmSystemStatus",
|
||||
value: "stay",
|
||||
displayed: false,
|
||||
isStateChange: true
|
||||
)
|
||||
}
|
||||
|
||||
// away security
|
||||
def awaySecurity() {
|
||||
sendLocationEvent(
|
||||
name: "alarmSystemStatus",
|
||||
value: "away",
|
||||
displayed: false,
|
||||
isStateChange: true
|
||||
)
|
||||
}
|
||||
|
||||
// sets window shade open temperature
|
||||
def setWindowShadeOpen() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setWindowShadeOpen command recieved ${deviceID}")
|
||||
windowshade.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating window shade because it is the one specified: ${deviceID}");
|
||||
it.open()
|
||||
} else {
|
||||
log.debug("NOT operting window shade because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets window shade close temperature
|
||||
def setWindowShadeClosed() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setWindowShadeClosed command recieved ${deviceID}")
|
||||
windowshade.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating window shade because it is the one specified: ${deviceID}");
|
||||
it.close()
|
||||
} else {
|
||||
log.debug("NOT operting window shade because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat heating temperature
|
||||
def setThermostatHeatTemp() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatHeat command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setHeatingSetpoint(params.temp)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat cooling temperature
|
||||
def setThermostatCoolTemp() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatCool command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setCoolingSetpoint(params.temp)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat off
|
||||
def setThermostatOff() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatOff command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.off()
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat to heat
|
||||
def setThermostatHeat() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatHeat command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.heat()
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat to cool
|
||||
def setThermostatCool() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatCool command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.cool()
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat mode
|
||||
def setThermostatMode() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatMode command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setThermostatMode(params.mode)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sets thermostat fan mode
|
||||
def setThermostatFanMode() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("setThermostatFanMode command recieved ${deviceID}")
|
||||
thermostat.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating thermostat because it is the one specified: ${deviceID}");
|
||||
it.setThermostatFanMode(params.mode)
|
||||
} else {
|
||||
log.debug("NOT operting thermostat because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sends an alarm strobe
|
||||
def strobeAlarm() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Alarm strobe command recieved ${deviceID}")
|
||||
alarm.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating alarm because it is the one specified: ${deviceID}");
|
||||
it.strobe()
|
||||
} else {
|
||||
log.debug("NOT operting alarm because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sends an alarm siren
|
||||
def sirenAlarm() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Alarm siren command recieved ${deviceID}")
|
||||
alarm.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating alarm because it is the one specified: ${deviceID}");
|
||||
it.siren()
|
||||
} else {
|
||||
log.debug("NOT operting alarm because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// disables an alarm siren
|
||||
def silenceAlarm() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Alarm silence command recieved ${deviceID}")
|
||||
alarm.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating alarm because it is the one specified: ${deviceID}");
|
||||
it.off()
|
||||
} else {
|
||||
log.debug("NOT operting alarm because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// opens a garage door
|
||||
def openGarage() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Open Garage command recieved ${deviceID}")
|
||||
garagedoor.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating garage door because it is the one specified: ${deviceID}");
|
||||
it.open()
|
||||
} else {
|
||||
log.debug("NOT operting garage door device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// closes a garage door
|
||||
def closeGarage() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Close Garage command recieved ${deviceID}")
|
||||
garagedoor.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating garage door because it is the one specified: ${deviceID}");
|
||||
it.close()
|
||||
} else {
|
||||
log.debug("NOT operting garage door device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// lock locks a door lock
|
||||
def lockDoor() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Lock command recieved ${deviceID}")
|
||||
lock.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating lock device because it is the one specified: ${deviceID}");
|
||||
it.lock()
|
||||
} else {
|
||||
log.debug("NOT operting lock device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unlock unlocks a door lock
|
||||
def unlockDoor() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Unlock command recieved ${deviceID}")
|
||||
lock.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating lock device because it is the one specified: ${deviceID}");
|
||||
it.unlock()
|
||||
} else {
|
||||
log.debug("NOT operting lock device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// turns on a wall switch as instructed from the homeai webservice
|
||||
def switchOn() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Switch on command recieved ${deviceID}")
|
||||
lightswitch.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating switch device because it is the one specified: ${deviceID}");
|
||||
it.on()
|
||||
} else {
|
||||
log.debug("Skipping switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// turns off a wall switch as instructed from the homeai webservice
|
||||
def switchOff() {
|
||||
def deviceID = params.deviceID
|
||||
log.debug("Switch off desired for ${deviceID}")
|
||||
lightswitch.each {
|
||||
if (it.id == deviceID) {
|
||||
log.debug("Operating switch device because it is the one specified: ${deviceID}");
|
||||
it.off()
|
||||
} else {
|
||||
log.debug("Skipping switch device because it is not the one specified: ${deviceID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the id of this smartthings hub
|
||||
def hubId() {
|
||||
log.debug("hub id requested.")
|
||||
def response = [hubId: location.hubs.id[0]]
|
||||
}
|
||||
|
||||
// This handles requests for device inventories
|
||||
def inventory() {
|
||||
def response = []
|
||||
|
||||
lightswitch.each {
|
||||
response << [name: it.displayName, value: it.currentValue("switch"), deviceId: it.id, type: "lightSwitch"]
|
||||
}
|
||||
|
||||
contact.each {
|
||||
response << [name: it.displayName, value: it.currentValue("contact"), deviceId: it.id, type: "contact"]
|
||||
}
|
||||
|
||||
|
||||
motion.each {
|
||||
response << [name: it.displayName, value: it.currentValue("motion"), deviceId: it.id, type: "motion"]
|
||||
}
|
||||
|
||||
presence.each {
|
||||
response << [name: it.displayName, value: it.currentValue("presence"), deviceId: it.id, type: "presence"]
|
||||
}
|
||||
|
||||
// removed until dual device functions are supported on the backend
|
||||
//tempSensor.each {
|
||||
// response << [name: it.displayName, value: it.currentValue("temperature"), deviceId: it.id, type: "tempSensor"]
|
||||
//}
|
||||
|
||||
//humidity.each {
|
||||
// response << [name: it.displayName, value: it.currentValue("humidity"), deviceId: it.id, type: "humiditySensor"]
|
||||
//}
|
||||
|
||||
|
||||
waterSensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("water"), deviceId: it.id, type: "waterSensor"]
|
||||
}
|
||||
|
||||
lock.each {
|
||||
response << [name: it.displayName, value: it.currentValue("lock"), deviceId: it.id, type: "lock"]
|
||||
}
|
||||
|
||||
|
||||
garagedoor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("door"), deviceId: it.id, type: "garagedoor"]
|
||||
}
|
||||
|
||||
|
||||
touchsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("touch"), deviceId: it.id, type: "touchsensor"]
|
||||
}
|
||||
|
||||
|
||||
speechparser.each {
|
||||
response << [name: it.displayName, value: it.currentValue("phraseSpoken"), deviceId: it.id, type: "speechparser"]
|
||||
}
|
||||
|
||||
|
||||
soundsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("sound"), deviceId: it.id, type: "sound"]
|
||||
}
|
||||
|
||||
|
||||
smokedetector.each {
|
||||
response << [name: it.displayName, value: it.currentValue("smoke"), deviceId: it.id, type: "smoke"]
|
||||
}
|
||||
|
||||
|
||||
sleepsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("sleeping"), deviceId: it.id, type: "sleepsensor"]
|
||||
}
|
||||
|
||||
|
||||
carbonsensor.each {
|
||||
response << [name: it.displayName, value: it.currentValue("carbonMonoxide"), deviceId: it.id, type: "carbonsensor"]
|
||||
}
|
||||
|
||||
|
||||
button.each {
|
||||
response << [name: it.displayName, value: it.currentValue("button"), deviceId: it.id, type: "button"]
|
||||
}
|
||||
|
||||
|
||||
beacon.each {
|
||||
response << [name: it.displayName, value: it.currentValue("presence"), deviceId: it.id, type: "beacon"]
|
||||
}
|
||||
|
||||
|
||||
alarm.each {
|
||||
response << [name: it.displayName, value: it.currentValue("alarm"), deviceId: it.id, type: "alarm"]
|
||||
}
|
||||
|
||||
|
||||
thermostat.each {
|
||||
response << [name: it.displayName, value: it.currentValue("thermostatMode"), deviceId: it.id, type: "thermostat"]
|
||||
}
|
||||
|
||||
|
||||
voltage.each {
|
||||
response << [name: it.displayName, value: it.currentValue("voltage"), deviceId: it.id, type: "voltage"]
|
||||
}
|
||||
|
||||
|
||||
windowshade.each {
|
||||
response << [name: it.displayName, value: it.currentValue("windowShade"), deviceId: it.id, type: "windowshade"]
|
||||
}
|
||||
|
||||
|
||||
powermeter.each {
|
||||
response << [name: it.displayName, value: it.currentValue("power"), deviceId: it.id, type: "powermeter"]
|
||||
}
|
||||
|
||||
|
||||
lightswitchlevel.each {
|
||||
response << [name: it.displayName, value: it.currentValue("level"), deviceId: it.id, type: "lightswitchlevel"]
|
||||
}
|
||||
|
||||
|
||||
|
||||
log.debug("Inventory request processed. Response: " + response)
|
||||
return response
|
||||
}
|
||||
|
||||
// After the user hits the 'install' button in the mobile app
|
||||
def installed() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
// After app settings are changed. All subscriptions are wiped before this is invoked by smartthings.
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
// This appears to be what the tutorials meant to use in the examples
|
||||
def initialize() {
|
||||
|
||||
// SHM subscription
|
||||
// evt.value will be "off", "stay", or "away"
|
||||
subscribe(location, "alarmSystemStatus", eventForwarder)
|
||||
|
||||
|
||||
// motion sensor subscription
|
||||
subscribe(motion, "motion", eventForwarder)
|
||||
|
||||
|
||||
// Contact sensor subscription
|
||||
subscribe(contact, "contact", eventForwarder)
|
||||
|
||||
|
||||
// power plug subscription
|
||||
subscribe(lightswitch, "switch", eventForwarder)
|
||||
|
||||
|
||||
// presence sensor subscription
|
||||
subscribe(presence, "presence", eventForwarder)
|
||||
|
||||
|
||||
// temperature sensor subscription
|
||||
subscribe(tempSensor, "temperature", eventForwarder)
|
||||
|
||||
|
||||
// water sensor subscription
|
||||
subscribe(waterSensor, "water", eventForwarder)
|
||||
|
||||
|
||||
// humidity sensor subscription
|
||||
subscribe(humidity, "humidity", eventForwarder)
|
||||
|
||||
|
||||
// lock subscription
|
||||
subscribe(lock, "lock", eventForwarder)
|
||||
|
||||
|
||||
// garage door subscription
|
||||
subscribe(garagedoor, "garagedoor", eventForwarder)
|
||||
|
||||
|
||||
// touch sensor subscription
|
||||
subscribe(touchsensor, "touchsensor", eventForwarder)
|
||||
|
||||
|
||||
// speech parser subscription
|
||||
subscribe(speechparser, "phraseSpoken", eventForwarder)
|
||||
|
||||
|
||||
// sound sensor subscription
|
||||
subscribe(soundsensor, "sound", eventForwarder)
|
||||
|
||||
|
||||
// smoke detector subscription
|
||||
subscribe(smokedetector, "smoke", eventForwarder)
|
||||
|
||||
|
||||
// sleep sensor subscription
|
||||
subscribe(sleepsensor, "sleeping", eventForwarder)
|
||||
|
||||
|
||||
// carbon monoxide sensor subscription
|
||||
subscribe(carbonsensor, "carbonMonoxide", eventForwarder)
|
||||
|
||||
|
||||
// button subscription
|
||||
subscribe(button, "button", eventForwarder)
|
||||
|
||||
|
||||
// beacon subscription
|
||||
subscribe(beacon, "presence", eventForwarder)
|
||||
|
||||
|
||||
// alarm subscription
|
||||
subscribe(alarm, "alarm", eventForwarder)
|
||||
|
||||
// thermostat subscriptions
|
||||
subscribe(thermostat, "temperature", eventForwarder)
|
||||
subscribe(thermostat, "heatingSetpoint", eventForwarder)
|
||||
subscribe(thermostat, "coolingSetpoint", eventForwarder)
|
||||
subscribe(thermostat, "thermostatSetpoint", eventForwarder)
|
||||
subscribe(thermostat, "thermostatMode", eventForwarder)
|
||||
subscribe(thermostat, "thermostatFanMode", eventForwarder)
|
||||
subscribe(thermostat, "thermostatOperatingState", eventForwarder)
|
||||
|
||||
// voltage subscription
|
||||
subscribe(voltage, "voltage", eventForwarder)
|
||||
|
||||
// window shade subscription
|
||||
subscribe(windowshade, "windowShade", eventForwarder)
|
||||
|
||||
// shm events
|
||||
subscribe(location, "alarmSystemStatus", shmEventForwarder)
|
||||
|
||||
// power meter subscription
|
||||
subscribe(powermeter, "power", eventForwarder)
|
||||
|
||||
// level switch (dimmer switch)
|
||||
subscribe(lightswitchlevel, "level", eventForwarder)
|
||||
}
|
||||
|
||||
def shmEventForwarder(evt) {
|
||||
// evt.value will be "off", "stay", or "away"
|
||||
log.debug("FORWARDING SHM CHANGE" + evt.value + " " + evt.hub.id)
|
||||
|
||||
def deviceState = evt.value
|
||||
def deviceId = "smarthomemonitor"
|
||||
def hubId = hubId()
|
||||
def params = [
|
||||
uri: "https://stage.app.home.ai",
|
||||
path: "/smartThingsPostback/shmStateChange/${hubId}/${deviceId}/${deviceState}"
|
||||
]
|
||||
log.info(params)
|
||||
httpGet(params)
|
||||
|
||||
}
|
||||
|
||||
// This is used to forward events to the home.ai webservice
|
||||
def eventForwarder(evt) {
|
||||
|
||||
def hubId = location.hubs.id[0]
|
||||
|
||||
log.debug(params.uri + " " + params.path)
|
||||
log.debug("FORWARDING EVENT" + evt.deviceId + " " + evt.value + " " + hubId)
|
||||
|
||||
def deviceId = evt.deviceId
|
||||
def deviceState = evt.value
|
||||
def params = [
|
||||
uri: "https://stage.app.home.ai",
|
||||
path: "/smartThingsPostback/stateChange/${hubId}/${deviceId}/${deviceState}"
|
||||
]
|
||||
log.info(params)
|
||||
httpGet(params)
|
||||
}
|
||||
|
||||
// Mappings that serve web requests against our smart app
|
||||
mappings {
|
||||
path("/inventory") {
|
||||
action: [
|
||||
GET: "inventory"
|
||||
]
|
||||
}
|
||||
path("/hubId") {
|
||||
action: [
|
||||
GET: "hubId"
|
||||
]
|
||||
}
|
||||
path("/switchOn/:deviceID") {
|
||||
action: [
|
||||
GET: "switchOn"
|
||||
]
|
||||
}
|
||||
path("/switchOff/:deviceID") {
|
||||
action: [
|
||||
GET: "switchOff"
|
||||
]
|
||||
}
|
||||
path("/lock/:deviceID") {
|
||||
action: [
|
||||
GET: "lockDoor"
|
||||
]
|
||||
}
|
||||
path("/unlock/:deviceID") {
|
||||
action: [
|
||||
GET: "unlockDoor"
|
||||
]
|
||||
}
|
||||
path("/opengarage/:deviceID") {
|
||||
action: [
|
||||
GET: "openGarage"
|
||||
]
|
||||
}
|
||||
path("/closegarage/:deviceID") {
|
||||
action: [
|
||||
GET: "closeGarage"
|
||||
]
|
||||
}
|
||||
path("/strobeAlarm/:deviceID") {
|
||||
action: [
|
||||
GET: "strobeAlarm"
|
||||
]
|
||||
}
|
||||
path("/sirenAlarm/:deviceID") {
|
||||
action: [
|
||||
GET: "sirenAlarm"
|
||||
]
|
||||
}
|
||||
path("/silenceAlarm/:deviceID") {
|
||||
action: [
|
||||
GET: "silenceAlarm"
|
||||
]
|
||||
}
|
||||
path("/setThermostatHeatTemp/:deviceID/:temp") {
|
||||
action: [
|
||||
GET: "setThermostatHeatTemp"
|
||||
]
|
||||
}
|
||||
path("/setThermostatCoolTemp/:deviceID/:temp") {
|
||||
action: [
|
||||
GET: "setThermostatCoolTemp"
|
||||
]
|
||||
}
|
||||
path("/setThermostatHeat/:deviceID") {
|
||||
action: [
|
||||
GET: "setThermostatHeat"
|
||||
]
|
||||
}
|
||||
path("/setThermostatCool/:deviceID") {
|
||||
action: [
|
||||
GET: "setThermostatCool"
|
||||
]
|
||||
}
|
||||
path("/setThermostatMode/:deviceID/:mode") {
|
||||
action: [
|
||||
GET: "setThermostatMode"
|
||||
]
|
||||
}
|
||||
path("/setThermostatFanMode/:deviceID/:mode") {
|
||||
action: [
|
||||
GET: "setThermostatFanMode"
|
||||
]
|
||||
}
|
||||
path("/closeWindowShade/:deviceID") {
|
||||
action: [
|
||||
GET: "setWindowShadeClosed"
|
||||
]
|
||||
}
|
||||
path("/openWindowShade/:deviceID") {
|
||||
action: [
|
||||
GET: "setWindowShadeOpen"
|
||||
]
|
||||
}
|
||||
path("/awaySecurity") {
|
||||
action: [
|
||||
GET: "awaySecurity"
|
||||
]
|
||||
}
|
||||
path("/staySecurity") {
|
||||
action: [
|
||||
GET: "staySecurity"
|
||||
]
|
||||
}
|
||||
path("/offSecurity") {
|
||||
action: [
|
||||
GET: "offSecurity"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -296,10 +296,7 @@ def pageHistory() {
|
||||
if (history.size() == 0) {
|
||||
paragraph "No history available."
|
||||
} else {
|
||||
history.each() {
|
||||
def text = "" + new Date(it.time + location.timeZone.rawOffset ).format("yyyy-MM-dd HH:mm") + ": " + it.event + " - " + it.description
|
||||
paragraph text
|
||||
}
|
||||
paragraph "Not implemented"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,7 +405,6 @@ def pageConfigureZones() {
|
||||
section("${it.displayName} (contact)") {
|
||||
input "type_${devId}", "enum", title:"Zone Type", metadata:[values:zoneTypes], defaultValue:"exterior"
|
||||
input "delay_${devId}", "bool", title:"Entry/Exit Delays", defaultValue:true
|
||||
input "chime_${devId}", "bool", title:"Chime on open", defaultValue:true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -420,7 +416,6 @@ def pageConfigureZones() {
|
||||
section("${it.displayName} (motion)") {
|
||||
input "type_${devId}", "enum", title:"Zone Type", metadata:[values:zoneTypes], defaultValue:"interior"
|
||||
input "delay_${devId}", "bool", title:"Entry/Exit Delays", defaultValue:false
|
||||
input "chime_${devId}", "bool", title:"Chime on motion", defaultValue:false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -432,7 +427,6 @@ def pageConfigureZones() {
|
||||
section("${it.displayName} (movement)") {
|
||||
input "type_${devId}", "enum", title:"Zone Type", metadata:[values:zoneTypes], defaultValue:"interior"
|
||||
input "delay_${devId}", "bool", title:"Entry/Exit Delays", defaultValue:false
|
||||
input "chime_${devId}", "bool", title:"Chime on movement", defaultValue:false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -444,7 +438,6 @@ def pageConfigureZones() {
|
||||
section("${it.displayName} (smoke)") {
|
||||
input "type_${devId}", "enum", title:"Zone Type", metadata:[values:zoneTypes], defaultValue:"alert"
|
||||
input "delay_${devId}", "bool", title:"Entry/Exit Delays", defaultValue:false
|
||||
input "chime_${devId}", "bool", title:"Chime on smoke", defaultValue:false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -456,7 +449,6 @@ def pageConfigureZones() {
|
||||
section("${it.displayName} (moisture)") {
|
||||
input "type_${devId}", "enum", title:"Zone Type", metadata:[values:zoneTypes], defaultValue:"alert"
|
||||
input "delay_${devId}", "bool", title:"Entry/Exit Delays", defaultValue:false
|
||||
input "chime_${devId}", "bool", title:"Chime on water", defaultValue:false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,14 +471,6 @@ def pageArmingOptions() {
|
||||
"is armed without setting off an alarm. You can optionally disable " +
|
||||
"entry and exit delay when the alarm is armed in Stay mode."
|
||||
|
||||
def inputKeypads = [
|
||||
name: "keypads",
|
||||
type: "capability.lockCodes",
|
||||
title: "Keypads for Exit / Entry delay",
|
||||
multiple: true,
|
||||
required: false
|
||||
]
|
||||
|
||||
def inputAwayModes = [
|
||||
name: "awayModes",
|
||||
type: "mode",
|
||||
@@ -523,19 +507,10 @@ def pageArmingOptions() {
|
||||
def inputDelayStay = [
|
||||
name: "stayDelayOff",
|
||||
type: "bool",
|
||||
title: "Disable alarm (entry) delay in Stay mode",
|
||||
title: "Disable delays in Stay mode",
|
||||
defaultValue: false,
|
||||
required: true
|
||||
]
|
||||
|
||||
def inputExitDelayStay = [
|
||||
name: "stayExitDelayOff",
|
||||
type: "bool",
|
||||
title: "Disable arming (exit) delay in Stay mode",
|
||||
defaultValue: true,
|
||||
required: true
|
||||
]
|
||||
|
||||
|
||||
def pageProperties = [
|
||||
name: "pageArmingOptions",
|
||||
@@ -549,21 +524,16 @@ def pageArmingOptions() {
|
||||
paragraph helpArming
|
||||
}
|
||||
|
||||
section("Keypads") {
|
||||
input inputKeypads
|
||||
}
|
||||
|
||||
section("Modes") {
|
||||
input inputAwayModes
|
||||
input inputStayModes
|
||||
input inputDisarmModes
|
||||
}
|
||||
|
||||
|
||||
section("Exit and Entry Delay") {
|
||||
paragraph helpDelay
|
||||
input inputDelay
|
||||
input inputDelayStay
|
||||
input inputExitDelayStay
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -593,14 +563,6 @@ def pageAlarmOptions() {
|
||||
defaultValue: "Both"
|
||||
]
|
||||
|
||||
def inputSirenEntryStrobe = [
|
||||
name: "sirenEntryStrobe",
|
||||
type: "bool",
|
||||
title: "Strobe siren during entry delay",
|
||||
defaultValue: true,
|
||||
required: true
|
||||
]
|
||||
|
||||
def inputSwitches = [
|
||||
name: "switches",
|
||||
type: "capability.switch",
|
||||
@@ -640,7 +602,6 @@ def pageAlarmOptions() {
|
||||
section("Sirens") {
|
||||
input inputAlarms
|
||||
input inputSirenMode
|
||||
input inputSirenEntryStrobe
|
||||
}
|
||||
section("Switches") {
|
||||
input inputSwitches
|
||||
@@ -663,49 +624,7 @@ def pageNotifications() {
|
||||
"disarmed or when an alarm is set off. Notifications can be send " +
|
||||
"using either Push messages, SMS (text) messages and Pushbullet " +
|
||||
"messaging service. Smart Alarm can also notify you with sounds or " +
|
||||
"voice alerts using compatible audio devices, such as Sonos." +
|
||||
"Or using a SmartAlarm dashboard virtual device."
|
||||
|
||||
def inputNotificationDevice = [
|
||||
name: "notificationDevice",
|
||||
type: "capability.notification",
|
||||
title: "Which smart alarm notification device?",
|
||||
multiple: false,
|
||||
required: false
|
||||
]
|
||||
|
||||
def inputChimeDevices = [
|
||||
name: "chimeDevices",
|
||||
type: "capability.tone",
|
||||
title: "Which Chime Devices?",
|
||||
multiple: true,
|
||||
required: false
|
||||
]
|
||||
|
||||
|
||||
def inputSirenOnWaterAlert = [
|
||||
name: "sirenOnWaterAlert",
|
||||
type: "bool",
|
||||
title: "Use Siren for Water Leak?",
|
||||
defaultValue: true,
|
||||
required: true
|
||||
]
|
||||
|
||||
def inputSirenOnSmokeAlert = [
|
||||
name: "sirenOnSmokeAlert",
|
||||
type: "bool",
|
||||
title: "Use Siren for Smoke Alert?",
|
||||
defaultValue: true,
|
||||
required: true
|
||||
]
|
||||
|
||||
def inputSirenOnIntrusionAlert = [
|
||||
name: "sirenOnIntrusionAlert",
|
||||
type: "bool",
|
||||
title: "Use Siren for Intrusion Alert?",
|
||||
defaultValue: true,
|
||||
required: true
|
||||
]
|
||||
"voice alerts using compatible audio devices, such as Sonos."
|
||||
|
||||
def inputPushAlarm = [
|
||||
name: "pushMessage",
|
||||
@@ -888,19 +807,6 @@ def pageNotifications() {
|
||||
section("Notification Options") {
|
||||
paragraph helpAbout
|
||||
}
|
||||
section("Notification Device")
|
||||
{
|
||||
input inputNotificationDevice
|
||||
}
|
||||
section("Chime Devices") {
|
||||
input inputChimeDevices
|
||||
}
|
||||
section("Siren Notifcations")
|
||||
{
|
||||
input inputSirenOnWaterAlert
|
||||
input inputSirenOnSmokeAlert
|
||||
input inputSirenOnIntrusionAlert
|
||||
}
|
||||
section("Push Notifications") {
|
||||
input inputPushAlarm
|
||||
input inputPushStatus
|
||||
@@ -1141,7 +1047,6 @@ private def setupInit() {
|
||||
state.zones = []
|
||||
state.alarms = []
|
||||
state.history = []
|
||||
state.alertType = "None"
|
||||
} else {
|
||||
def version = state.version as String
|
||||
if (version == null || version.startsWith('1')) {
|
||||
@@ -1160,7 +1065,7 @@ private def initialize() {
|
||||
clearAlarm()
|
||||
state.delay = settings.delay?.toInteger() ?: 30
|
||||
state.offSwitches = []
|
||||
//state.history = []
|
||||
state.history = []
|
||||
|
||||
if (settings.awayModes?.contains(location.mode)) {
|
||||
state.armed = true
|
||||
@@ -1177,20 +1082,8 @@ private def initialize() {
|
||||
initButtons()
|
||||
initRestApi()
|
||||
subscribe(location, onLocation)
|
||||
|
||||
if (settings.notificationDevice)
|
||||
{
|
||||
subscribe(settings.notificationDevice, "switch.off", gotDismissMessage)
|
||||
}
|
||||
|
||||
STATE()
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
def gotDismissMessage(evt)
|
||||
{
|
||||
log.debug "Got the dismiss message from the notification device.. clearing alarm!"
|
||||
clearAlarm()
|
||||
}
|
||||
|
||||
private def clearAlarm() {
|
||||
@@ -1210,7 +1103,6 @@ private def clearAlarm() {
|
||||
}
|
||||
state.offSwitches = []
|
||||
}
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
private def initZones() {
|
||||
@@ -1231,8 +1123,7 @@ private def initZones() {
|
||||
deviceId: it.id,
|
||||
sensorType: "contact",
|
||||
zoneType: settings["type_${it.id}"] ?: "exterior",
|
||||
delay: settings["delay_${it.id}"],
|
||||
chime: settings["chime_${it.id}"]
|
||||
delay: settings["delay_${it.id}"]
|
||||
]
|
||||
}
|
||||
subscribe(settings.z_contact, "contact.open", onContact)
|
||||
@@ -1244,8 +1135,7 @@ private def initZones() {
|
||||
deviceId: it.id,
|
||||
sensorType: "motion",
|
||||
zoneType: settings["type_${it.id}"] ?: "interior",
|
||||
delay: settings["delay_${it.id}"],
|
||||
chime: settings["chime_${it.id}"]
|
||||
delay: settings["delay_${it.id}"]
|
||||
]
|
||||
}
|
||||
subscribe(settings.z_motion, "motion.active", onMotion)
|
||||
@@ -1257,8 +1147,7 @@ private def initZones() {
|
||||
deviceId: it.id,
|
||||
sensorType: "acceleration",
|
||||
zoneType: settings["type_${it.id}"] ?: "interior",
|
||||
delay: settings["delay_${it.id}"],
|
||||
chime: settings["chime_${it.id}"]
|
||||
delay: settings["delay_${it.id}"]
|
||||
]
|
||||
}
|
||||
subscribe(settings.z_movement, "acceleration.active", onMovement)
|
||||
@@ -1270,8 +1159,7 @@ private def initZones() {
|
||||
deviceId: it.id,
|
||||
sensorType: "smoke",
|
||||
zoneType: settings["type_${it.id}"] ?: "alert",
|
||||
delay: settings["delay_${it.id}"],
|
||||
chime: settings["chime_${it.id}"]
|
||||
delay: settings["delay_${it.id}"]
|
||||
]
|
||||
}
|
||||
subscribe(settings.z_smoke, "smoke.detected", onSmoke)
|
||||
@@ -1286,8 +1174,7 @@ private def initZones() {
|
||||
deviceId: it.id,
|
||||
sensorType: "water",
|
||||
zoneType: settings["type_${it.id}"] ?: "alert",
|
||||
delay: settings["delay_${it.id}"],
|
||||
chime: settings["chime_${it.id}"]
|
||||
delay: settings["delay_${it.id}"]
|
||||
]
|
||||
}
|
||||
subscribe(settings.z_water, "water.wet", onWater)
|
||||
@@ -1370,37 +1257,20 @@ def onWater(evt) { onZoneEvent(evt, "water") }
|
||||
private def onZoneEvent(evt, sensorType) {
|
||||
LOG("onZoneEvent(${evt.displayName}, ${sensorType})")
|
||||
|
||||
state.alertType = sensorType
|
||||
def zone = getZoneForDevice(evt.deviceId, sensorType)
|
||||
if (!zone) {
|
||||
log.warn "Cannot find zone for device ${evt.deviceId}"
|
||||
state.alertType = "None"
|
||||
return
|
||||
}
|
||||
|
||||
if (zone.armed) {
|
||||
state.alarms << evt.displayName
|
||||
if (zone.zoneType == "alert" || !zone.delay || (state.stay && settings.stayDelayOff)) {
|
||||
history("Alarm", "Alarm triggered by ${sensorType} sensor ${evt.displayName}")
|
||||
activateAlarm()
|
||||
} else {
|
||||
history("Entry Delay", "Entry delay triggered by ${sensorType} sensor ${evt.displayName}")
|
||||
if(settings.sirenEntryStrobe)
|
||||
{
|
||||
settings.alarms*.strobe()
|
||||
}
|
||||
keypads?.each() { it.setEntryDelay(state.delay) }
|
||||
myRunIn(state.delay, activateAlarm)
|
||||
}
|
||||
}
|
||||
else if (zone.chime)
|
||||
{
|
||||
chimeDevices?.each() {
|
||||
it.beep()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
def onLocation(evt) {
|
||||
@@ -1441,27 +1311,23 @@ def onButtonEvent(evt) {
|
||||
|
||||
def armAway() {
|
||||
LOG("armAway()")
|
||||
history("Armed Away", "Alarm armed away")
|
||||
|
||||
if (!atomicState.armed || atomicState.stay) {
|
||||
armPanel(false)
|
||||
}
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
def armStay() {
|
||||
LOG("armStay()")
|
||||
history("Armed Stay", "Alarm armed stay")
|
||||
|
||||
if (!atomicState.armed || !atomicState.stay) {
|
||||
armPanel(true)
|
||||
}
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
def disarm() {
|
||||
LOG("disarm()")
|
||||
history("Disarmed", "Alarm disarmed")
|
||||
|
||||
if (atomicState.armed) {
|
||||
state.armed = false
|
||||
state.zones.each() {
|
||||
@@ -1469,12 +1335,9 @@ def disarm() {
|
||||
it.armed = false
|
||||
}
|
||||
}
|
||||
|
||||
keypads?.each() { it.setDisarmed() }
|
||||
|
||||
reset()
|
||||
}
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
def panic() {
|
||||
@@ -1501,7 +1364,6 @@ def reset() {
|
||||
|
||||
notify(msg)
|
||||
notifyVoice()
|
||||
reportStatus()
|
||||
}
|
||||
|
||||
def exitDelayExpired() {
|
||||
@@ -1521,16 +1383,6 @@ def exitDelayExpired() {
|
||||
it.armed = true
|
||||
}
|
||||
}
|
||||
|
||||
if(stay)
|
||||
{
|
||||
keypads?.each() { it.setArmedStay() }
|
||||
}
|
||||
else
|
||||
{
|
||||
keypads?.each() { it.setArmedAway() }
|
||||
}
|
||||
|
||||
|
||||
def msg = "${location.name}: all "
|
||||
if (stay) {
|
||||
@@ -1554,7 +1406,7 @@ private def armPanel(stay) {
|
||||
state.zones.each() {
|
||||
def zoneType = it.zoneType
|
||||
if (zoneType == "exterior") {
|
||||
if (it.delay && !(stay && settings.stayExitDelayOff)) {
|
||||
if (it.delay) {
|
||||
it.armed = false
|
||||
armDelay = true
|
||||
} else {
|
||||
@@ -1572,22 +1424,10 @@ private def armPanel(stay) {
|
||||
}
|
||||
}
|
||||
|
||||
def delay = armDelay && !(stay && settings.stayExitDelayOff) ? atomicState.delay : 0
|
||||
def delay = armDelay && !(stay && settings.stayDelayOff) ? atomicState.delay : 0
|
||||
if (delay) {
|
||||
keypads?.each() { it.setExitDelay(delay) }
|
||||
myRunIn(delay, exitDelayExpired)
|
||||
}
|
||||
else
|
||||
{
|
||||
if(stay)
|
||||
{
|
||||
keypads?.each() { it.setArmedStay() }
|
||||
}
|
||||
else
|
||||
{
|
||||
keypads?.each() { it.setArmedAway() }
|
||||
}
|
||||
}
|
||||
|
||||
def mode = stay ? "STAY" : "AWAY"
|
||||
def msg = "${location.name} "
|
||||
@@ -1692,50 +1532,21 @@ def activateAlarm() {
|
||||
log.warn "activateAlarm: false alarm"
|
||||
return
|
||||
}
|
||||
history("Alarm", "Alarm Triggered")
|
||||
|
||||
if(settings.sirenEntryStrobe)
|
||||
{
|
||||
settings.alarms*.off()
|
||||
switch (settings.sirenMode) {
|
||||
case "Siren":
|
||||
settings.alarms*.siren()
|
||||
break
|
||||
|
||||
case "Strobe":
|
||||
settings.alarms*.strobe()
|
||||
break
|
||||
|
||||
case "Both":
|
||||
settings.alarms*.both()
|
||||
break
|
||||
}
|
||||
|
||||
def atype = state.alertType
|
||||
|
||||
if ((atype == "Water" && settings.sirenOnWaterAlert) ||
|
||||
(atype == "Smoke" && settings.sirenOnSmokeAlert) ||
|
||||
((atype == "contact" || atype == "acceleration" || atype == "motion") && settings.sirenOnIntrusionAlert))
|
||||
{
|
||||
switch (settings.sirenMode) {
|
||||
case "Siren":
|
||||
settings.alarms*.siren()
|
||||
break
|
||||
|
||||
case "Strobe":
|
||||
settings.alarms*.strobe()
|
||||
break
|
||||
|
||||
case "Both":
|
||||
settings.alarms*.both()
|
||||
break
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.debug "No siren for $atype Alert"
|
||||
}
|
||||
}
|
||||
|
||||
def activateAlarmPostDelay(String lastAlertType)
|
||||
{
|
||||
// no alarm check here as if door opens only for second with delay and system is not disarmed
|
||||
// we still want alarm even if door is closed after delay.. Basically like real alarm the delay is only
|
||||
// to disarm the system. Otherwise someone can open door come it, quickly close and there is not alarm LGK.
|
||||
|
||||
// issue here is that after delay we could have lost the alert type so pass it in
|
||||
log.debug "activate alarm post delay check - alert type = $lastAlertType"
|
||||
|
||||
activateSirenAfterCheck(lastAlertType)
|
||||
|
||||
// Only turn on those switches that are currently off
|
||||
def switchesOn = settings.switches?.findAll { it?.currentSwitch == "off" }
|
||||
LOG("switchesOn: ${switchesOn}")
|
||||
@@ -1759,8 +1570,6 @@ def activateAlarmPostDelay(String lastAlertType)
|
||||
notify(msg)
|
||||
notifyVoice()
|
||||
|
||||
reportStatus()
|
||||
|
||||
myRunIn(180, reset)
|
||||
}
|
||||
|
||||
@@ -1859,61 +1668,12 @@ private def notifyVoice() {
|
||||
}
|
||||
}
|
||||
|
||||
def reportStatus()
|
||||
{
|
||||
log.debug "in report status"
|
||||
log.debug "notification device = ${settings.notificationDevice}"
|
||||
|
||||
if (settings.notificationDevice)
|
||||
{
|
||||
def phrase = ""
|
||||
if (state.alarms.size())
|
||||
{
|
||||
phrase = "Alert: Alarm at ${location.name}!"
|
||||
notificationDevice.deviceNotification(phrase)
|
||||
log.debug "sending notification alert: = $phrase"
|
||||
def zones = "Zones: "
|
||||
state.alarms.each()
|
||||
{
|
||||
//log.debug "in loop it"
|
||||
//log.debug "it = $it"
|
||||
zones = "Zones: "
|
||||
zones += " $it" +"\n"
|
||||
}
|
||||
notificationDevice.deviceNotification(zones)
|
||||
log.debug "sending nofication zones = $zones"
|
||||
|
||||
// send zone type
|
||||
phrase = "AlertType: "
|
||||
def atype = state.alertType
|
||||
if (atype == null)
|
||||
atype = "None"
|
||||
phrase += " $atype"
|
||||
notificationDevice.deviceNotification(phrase)
|
||||
log.debug "sending nofication alert type = $phrase"
|
||||
}
|
||||
else
|
||||
{
|
||||
phrase = "Status: "
|
||||
if (state.armed)
|
||||
{
|
||||
def mode = state.stay ? "Armed - Stay" : "Armed - Away"
|
||||
phrase += "${mode}"
|
||||
} else {
|
||||
phrase += "Disarmed"
|
||||
}
|
||||
log.debug "sending notification status = $phrase"
|
||||
notificationDevice.deviceNotification(phrase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def history(String event, String description = "") {
|
||||
LOG("history(${event}, ${description})")
|
||||
|
||||
def history = atomicState.history
|
||||
history << [time: now(), event: event, description: description]
|
||||
if (history.size() > 20) {
|
||||
if (history.size() > 10) {
|
||||
history = history.sort{it.time}
|
||||
history = history[1..-1]
|
||||
}
|
||||
@@ -2090,27 +1850,3 @@ private def LOG(message) {
|
||||
private def STATE() {
|
||||
//log.trace "state: ${state}"
|
||||
}
|
||||
|
||||
def onAlarmSystemStatus(evt) {
|
||||
LOG("Alarm System Status has been changed to '${evt.value}'")
|
||||
String mode = evt.value.toLowerCase()
|
||||
if (mode == "away") {
|
||||
armAway()
|
||||
} else if (mode == "stay") {
|
||||
armStay()
|
||||
} else if (mode == "off") {
|
||||
disarm()
|
||||
}
|
||||
}
|
||||
|
||||
def setAlarmMode(name) {
|
||||
LOG("Alarm System Status will be set to '${name}'")
|
||||
def event = [
|
||||
name: "alarmSystemStatus",
|
||||
value: name,
|
||||
isStateChange: true,
|
||||
displayed: true,
|
||||
description: "alarm system status is ${name}",
|
||||
]
|
||||
sendLocationEvent(event)
|
||||
}
|
||||
Reference in New Issue
Block a user