Compare commits

..

1 Commits

Author SHA1 Message Date
JC Wall
cab4456443 MSA-1987: Kwikset 2017-05-17 21:00:37 -07:00
25 changed files with 2018 additions and 992 deletions

View File

@@ -39,8 +39,8 @@ metadata {
}
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
attributeState("active", label:'tamper active', backgroundColor:"#00A0DC")
attributeState("inactive", label:'tamper inactive', backgroundColor:"#CCCCCC")
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
}
}

View File

@@ -37,8 +37,8 @@ metadata {
}
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
attributeState("active", label:'tamper active', backgroundColor:"#00A0DC")
attributeState("inactive", label:'tamper inactive', backgroundColor:"#CCCCCC")
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
}
}

View File

@@ -21,7 +21,6 @@ metadata {
capability "Battery"
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x80,0x84,0x85"
fingerprint mfr: "0086", prod: "0001", model: "0026", deviceJoinName: "Aeon Panic Button"
}
simulator {
@@ -131,12 +130,5 @@ def updated() {
}
def initialize() {
def zwMap = getZwaveInfo()
def buttons = 4 // Default for Key Fob
// Only one button for Aeon Panic Button
if (zwMap && zwMap.mfr == "0086" && zwMap.prod == "0001" && zwMap.model == "0026") {
buttons = 1
}
sendEvent(name: "numberOfButtons", value: buttons)
sendEvent(name: "numberOfButtons", value: 4)
}

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,43 +0,0 @@
# Aeon Multisensor 6
Cloud Execution
Works with:
* [Aeon Labs MultiSensor 6](https://www.smartthings.com/products/aeon-labs-multisensor-6)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Troubleshooting](#troubleshooting)
## Capabilities
* **Motion Sensor** - can detect motion
* **Temperature Measurement** - defines device measures current temperature
* **Relative Humidity Measurement** - allow reading the relative humidity from devices that support it
* **Illuminance Measurement** - gives the illuminance reading from devices that support it
* **Ultraviolet Index** - gives the ability to get the ultraviolet index from devices that report it
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Sensor** - detects sensor events
* **Battery** - defines device uses a battery
* **Health Check** - indicates ability to get device health notifications
## Device Health
Aeon Labs MultiSensor 6 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:
* [Aeon Labs MultiSensor 6 Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206157226)

View File

@@ -22,7 +22,6 @@ metadata {
capability "Configuration"
capability "Sensor"
capability "Battery"
capability "Health Check"
attribute "tamper", "enum", ["detected", "clear"]
attribute "batteryStatus", "string"
@@ -30,7 +29,6 @@ metadata {
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A,0x5A"
fingerprint mfr:"0086", prod:"0102", model:"0064", deviceJoinName: "Aeon Labs MultiSensor 6"
}
simulator {
@@ -129,14 +127,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])
log.debug "Updated with settings: ${settings}"
log.debug "${device.displayName} is now ${device.latestValue("powerSupply")}"
@@ -335,13 +326,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
createEvent(descriptionText: cmd.toString(), isStateChange: false)
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
secure(zwave.batteryV1.batteryGet())
}
def configure() {
// This sensor joins as a secure device if you double-click the button to include it
log.debug "${device.displayName} is configuring its settings"

View File

@@ -9,7 +9,6 @@ metadata {
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Health Check"
attribute "thermostatFanState", "string"
@@ -19,7 +18,6 @@ metadata {
command "quickSetHeat"
fingerprint deviceId: "0x08", inClusters: "0x43,0x40,0x44,0x31,0x80,0x85,0x60"
fingerprint mfr:"0098", prod:"6401", model:"0107", deviceJoinName: "2Gig CT100 Programmable Thermostat"
}
// simulator metadata
@@ -108,16 +106,6 @@ metadata {
}
}
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 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 parse(String description)
{
def result = []
@@ -451,14 +439,6 @@ def setCoolingSetpoint(Double degrees, Integer delay = 30000) {
], delay)
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
log.debug "ping() called"
refresh()
}
def configure() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSupportedGet().format(),

View File

@@ -107,8 +107,8 @@
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
standardTile("acceleration", "device.acceleration") {
state("active", label:'vibration', icon:"st.motion.acceleration.active", backgroundColor:"#00a0dc")
state("inactive", label:'still', icon:"st.motion.acceleration.inactive", backgroundColor:"#cccccc")
state("active", label:'vibration', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
state("inactive", label:'still', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
}

View File

@@ -12,13 +12,13 @@
*
*/
metadata {
definition (name: "Fortrezz Water Valve", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.watervalve") {
definition (name: "Fortrezz Water Valve", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Health Check"
capability "Valve"
capability "Refresh"
capability "Sensor"
fingerprint deviceId: "0x1000", inClusters: "0x25,0x72,0x86,0x71,0x22,0x70"
fingerprint mfr:"0084", prod:"0213", model:"0215", deviceJoinName: "FortrezZ Water Valve"
}
@@ -34,22 +34,19 @@ metadata {
}
// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC"
attributeState "closing", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
}
tiles {
standardTile("contact", "device.contact", width: 2, height: 2, canChangeIcon: true) {
state "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
state "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
state "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC"
state "closing", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.valve", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "valve"
details(["valve","refresh"])
main "contact"
details(["contact","refresh"])
}
}
@@ -65,23 +62,22 @@ def updated(){
def parse(String description) {
log.trace description
def result = null
def cmd = zwave.parse(description)
if (cmd) {
return zwaveEvent(cmd)
result = createEvent(zwaveEvent(cmd))
}
log.debug "Could not parse message"
return null
log.debug "Parse returned ${result?.descriptionText}"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
def value = cmd.value ? "closed" : "open"
return [createEventWithDebug([name: "contact", value: value, descriptionText: "$device.displayName valve is $value"]),
createEventWithDebug([name: "valve", value: value, descriptionText: "$device.displayName valve is $value"])]
[name: "contact", value: value, descriptionText: "$device.displayName valve is $value"]
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
return createEvent([:]) // Handles all Z-Wave commands we aren't interested in
[:] // Handles all Z-Wave commands we aren't interested in
}
def open() {
@@ -102,9 +98,3 @@ def ping() {
def refresh() {
zwave.switchBinaryV1.switchBinaryGet().format()
}
def createEventWithDebug(eventMap) {
def event = createEvent(eventMap)
log.debug "Event created with ${event?.descriptionText}"
return event
}

View File

@@ -61,8 +61,8 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name: "motion", type: "generic", width: 6, height: 4) {
tileAttribute("device.motion", key: "PRIMARY_CONTROL") {
attributeState "active", label: 'motion', icon: "st.motion.motion.active", backgroundColor: "#00A0DC"
attributeState "inactive", label: 'no motion', icon: "st.motion.motion.inactive", backgroundColor: "#cccccc"
attributeState "active", label: 'motion', icon: "st.motion.motion.active", backgroundColor: "#53a7c0"
attributeState "inactive", label: 'no motion', icon: "st.motion.motion.inactive", backgroundColor: "#ffffff"
}
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {

View File

@@ -87,8 +87,8 @@ metadata {
state("closed", label: 'Closed', icon: "st.contact.contact.closed", backgroundColor: "#00a0dc")
}
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
state("active", label: 'Active', icon: "st.motion.acceleration.active", backgroundColor: "#00a0dc")
state("inactive", label: 'Inactive', icon: "st.motion.acceleration.inactive", backgroundColor: "#cccccc")
state("active", label: 'Active', icon: "st.motion.acceleration.active", backgroundColor: "#53a7c0")
state("inactive", label: 'Inactive', icon: "st.motion.acceleration.inactive", backgroundColor: "#ffffff")
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label: '${currentValue}°',

View File

@@ -46,8 +46,8 @@
}
standardTile("motion", "device.motion", width: 2, height: 2) {
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")
state("offline", label:'${name}', icon:"st.motion.motion.inactive", backgroundColor:"#ff0000")
}

View File

@@ -34,7 +34,7 @@ import physicalgraph.zigbee.zcl.DataType
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD226/246 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD226/246 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
}
tiles(scale: 2) {

View File

@@ -39,7 +39,7 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
@@ -83,9 +83,6 @@ def parse(String description) {
}
}
sendEvent(event)
//handle valve attribute
event.name = "valve"
sendEvent(event)
}
else {
def descMap = zigbee.parseDescriptionAsMap(description)

View File

@@ -30,15 +30,9 @@ metadata {
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+
fingerprint mfr:"0086", prod:"0002", model:"001D", deviceJoinName: "Aeon Labs Door/Window Sensor (Gen 5)"
fingerprint mfr:"0086", prod:"0102", model:"0070", deviceJoinName: "Aeon Labs Door/Window Sensor 6"
fingerprint mfr:"0086", prod:"0102", model:"0059", deviceJoinName: "Aeon Labs Recessed Door Sensor"
fingerprint mfr:"014A", prod:"0001", model:"0002", deviceJoinName: "Ecolink Door/Window Sensor"
fingerprint mfr:"014A", prod:"0001", model:"0003", deviceJoinName: "Ecolink Tilt Sensor"
fingerprint mfr:"0086", prod:"0102", model:"0070", deviceJoinName: "Aeon Labs Door/Window Sensor 6"
fingerprint mfr:"011A", prod:"0601", model:"0903", deviceJoinName: "Enerwave Magnetic Door/Window Sensor"
fingerprint mfr:"014F", prod:"2001", model:"0102", deviceJoinName: "Nortek GoControl Door/Window Sensor"
fingerprint mfr:"0063", prod:"4953", model:"3031", deviceJoinName: "Jasco Hinge Pin Door Sensor"
fingerprint mfr:"019A", prod:"0003", model:"0003", deviceJoinName: "Sensative Strips"
}
// simulator metadata

View File

@@ -26,24 +26,7 @@ metadata {
fingerprint deviceId: "0x4003", inClusters: "0x98"
fingerprint deviceId: "0x4004", inClusters: "0x98"
fingerprint mfr:"0090", prod:"0001", model:"0236", deviceJoinName: "KwikSet SmartCode 910 Deadbolt Door Lock"
fingerprint mfr:"0090", prod:"0003", model:"0238", deviceJoinName: "KwikSet SmartCode 910 Deadbolt Door Lock"
fingerprint mfr:"0090", prod:"0001", model:"0001", deviceJoinName: "KwikSet SmartCode 910 Contemporary Deadbolt Door Lock"
fingerprint mfr:"0090", prod:"0003", model:"0339", deviceJoinName: "KwikSet SmartCode 912 Lever Door Lock"
fingerprint mfr:"0090", prod:"0003", model:"4006", deviceJoinName: "KwikSet SmartCode 914 Deadbolt Door Lock" //backlit version
fingerprint mfr:"0090", prod:"0003", model:"0440", deviceJoinName: "KwikSet SmartCode 914 Deadbolt Door Lock"
fingerprint mfr:"0090", prod:"0001", model:"0642", deviceJoinName: "KwikSet SmartCode 916 Touchscreen Deadbolt Door Lock"
fingerprint mfr:"0090", prod:"0003", model:"0642", deviceJoinName: "KwikSet SmartCode 916 Touchscreen Deadbolt Door Lock"
fingerprint mfr:"003B", prod:"6341", model:"0544", deviceJoinName: "Schlage Camelot Touchscreen Deadbolt Door Lock"
fingerprint mfr:"003B", prod:"6341", model:"5044", deviceJoinName: "Schlage Century Touchscreen Deadbolt Door Lock"
fingerprint mfr:"003B", prod:"634B", model:"504C", deviceJoinName: "Schlage Connected Keypad Lever Door Lock"
fingerprint mfr:"0129", prod:"0002", model:"0800", deviceJoinName: "Yale Touchscreen Deadbolt Door Lock" // YRD120
fingerprint mfr:"0129", prod:"0002", model:"0000", deviceJoinName: "Yale Touchscreen Deadbolt Door Lock" // YRD220, YRD240
fingerprint mfr:"0129", prod:"0002", model:"FFFF", deviceJoinName: "Yale Touchscreen Lever Door Lock" // YRD220
fingerprint mfr:"0129", prod:"0004", model:"0800", deviceJoinName: "Yale Push Button Deadbolt Door Lock" // YRD110
fingerprint mfr:"0129", prod:"0004", model:"0000", deviceJoinName: "Yale Push Button Deadbolt Door Lock" // YRD210
fingerprint mfr:"0129", prod:"0001", model:"0000", deviceJoinName: "Yale Push Button Lever Door Lock" // YRD210
fingerprint mfr:"0129", prod:"8002", model:"0600", deviceJoinName: "Yale Assure Lock with Bluetooth"
fingerprint mfr:"0129", prod:"0002", model:"0000", deviceJoinName: "Yale Key Free Touchscreen Deadbolt"
}
simulator {

View File

@@ -29,7 +29,7 @@ metadata {
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
fingerprint mfr: "0063", prod: "4953", model: "3133", deviceJoinName: "GE Portable Smart Motion Sensor"
fingerprint mfr: "0063", prod: "4953", model: "3133", deviceJoinName: "GE Smart Motion Sensor"
}
simulator {

View File

@@ -37,8 +37,8 @@ metadata {
tiles {
standardTile("motion", "device.motion", width: 3, height: 2) {
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", inactiveLabel: false) {

View File

@@ -22,7 +22,6 @@ metadata {
attribute "alarmState", "string"
fingerprint deviceId: "0xA100", inClusters: "0x20,0x80,0x70,0x85,0x71,0x72,0x86"
fingerprint mfr:"0138", prod:"0001", model:"0001", deviceJoinName: "First Alert Smoke Detector"
fingerprint mfr:"0138", prod:"0001", model:"0002", deviceJoinName: "First Alert Smoke Detector and Carbon Monoxide Alarm (ZCOMBO)"
}

View File

@@ -12,7 +12,7 @@
*
*/
metadata {
definition (name: "Z-Wave Water Valve", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.watervalve") {
definition (name: "Z-Wave Water Valve", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Health Check"
capability "Valve"
@@ -37,7 +37,7 @@ metadata {
// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC"
@@ -45,7 +45,7 @@ metadata {
}
}
standardTile("refresh", "device.valve", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("refresh", "device.contact", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
@@ -68,19 +68,18 @@ def updated() {
def parse(String description) {
log.trace "parse description : $description"
def result = null
def cmd = zwave.parse(description, [0x20: 1])
if (cmd) {
return zwaveEvent(cmd)
result = createEvent(zwaveEvent(cmd))
}
log.debug "Could not parse message"
return null
log.debug "Parse returned ${result?.descriptionText}"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
def value = cmd.value == 0xFF ? "open" : cmd.value == 0x00 ? "closed" : "unknown"
return [createEventWithDebug([name: "contact", value: value, descriptionText: "$device.displayName valve is $value"]),
createEventWithDebug([name: "valve", value: value, descriptionText: "$device.displayName valve is $value"])]
def value = cmd.value == 0xFF ? "open" : cmd.value == 0x00 ? "closed" : "unknown"
[name: "contact", value: value, descriptionText: "$device.displayName valve is $value"]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { //TODO should show MSR when device is discovered
@@ -90,22 +89,20 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
log.debug "productTypeId: ${cmd.productTypeId}"
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
updateDataValue("MSR", msr)
return createEventWithDebug([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
[descriptionText: "$device.displayName MSR: $msr", isStateChange: false]
}
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
return createEventWithDebug([descriptionText: cmd.toString(), isStateChange: true, displayed: true])
[descriptionText: cmd.toString(), isStateChange: true, displayed: true]
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
def value = cmd.value == 0xFF ? "open" : cmd.value == 0x00 ? "closed" : "unknown"
return [createEventWithDebug([name: "contact", value: value, descriptionText: "$device.displayName valve is $value"]),
createEventWithDebug([name: "valve", value: value, descriptionText: "$device.displayName valve is $value"])]
[name: "contact", value: value, descriptionText: "$device.displayName valve is $value"]
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
return createEvent([:]) // Handles all Z-Wave commands we aren't interested in
[:] // Handles all Z-Wave commands we aren't interested in
}
def open() {
@@ -141,9 +138,3 @@ def refresh() {
}
delayBetween(commands,100)
}
def createEventWithDebug(eventMap) {
def event = createEvent(eventMap)
log.debug "Event created with ${event?.descriptionText}"
return event
}

View File

@@ -1,794 +0,0 @@
/**
* Zwave Thermostat Manager
*
* Credits and Kudos: this app is largely based on the more popular Thermostat Director SA by Tim Slagle -
* many thanks to @slagle for his continued support.
* Without his brilliance, this app would not exist!
*
*
* 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:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "ZWave Thermostat Manager",
namespace: "BD",
author: "Bobby Dobrescu",
description: "Adjust zwave thermostats based on a temperature range of a specific temperature sensor",
category: "My apps",
iconUrl: "http://icons.iconarchive.com/icons/icons8/windows-8/512/Science-Temperature-icon.png",
iconX2Url: "http://icons.iconarchive.com/icons/icons8/windows-8/512/Science-Temperature-icon.png"
)
preferences {
page name:"pageSetup"
page name:"TemperatureSettings"
page name:"ThermostatandDoors"
page name:"ThermostatAway"
page name:"Settings"
}
// Show setup page
def pageSetup() {
def pageProperties = [
name: "pageSetup",
title: "",
nextPage: null,
install: true,
uninstall: true
]
return dynamicPage(pageProperties) {
section("General Settings") {
href "TemperatureSettings", title: "Ambiance", description: "", state:greyedOut()
href "ThermostatandDoors", title: "Disabled Mode", description: "", state: greyedOutTherm()
href "ThermostatAway", title: "Away Mode", description: "", state: greyedOutTherm2()
href "Settings", title: "Other Settings", description: "", state: greyedOutSettings()
}
section([title:"Options", mobileOnly:true]) {
label title:"Assign a name", required:false
}
}
}
// Page - Temperature Settings
def TemperatureSettings() {
def sensor = [
name: "sensor",
type: "capability.temperatureMeasurement",
title: "Which Temperature Sensor(s)?",
multiple: true,
required: false
]
def thermostat = [
name: "thermostat",
type: "capability.thermostat",
title: "Which Thermostat?",
multiple: false,
required: true
]
def setLow = [
name: "setLow",
type: "number",
title: "Low temp?",
required: true
]
def cold = [
name: "cold",
type: "enum",
title: "Mode?",
required: false,
metadata: [values:["auto", "heat", "cool", "off"]]
]
def SetHeatingLow = [
name: "SetHeatingLow",
type: "number",
title: "Heating Temperature (degrees)",
required: true
]
def SetCoolingLow = [
name: "SetCoolingLow",
type: "number",
title: "Cooling Temperature (degrees)",
required: false
]
def setHigh = [
name: "setHigh",
type: "number",
title: "High temp?",
required: true
]
def hot = [
name: "hot",
type: "enum",
title: "Mode?",
required: false,
metadata: [values:["auto", "heat", "cool", "off"]]
]
def SetHeatingHigh = [
name: "SetHeatingHigh",
type: "number",
title: "Heating Temperature (degrees)",
required: false
]
def SetCoolingHigh = [
name: "SetCoolingHigh",
type: "number",
title: "Cooling Temperature (degrees)",
required: true
]
def pageName = "Ambiance"
def pageProperties = [
name: "TemperatureSettings",
title: "",
//nextPage: "ThermostatandDoors"
]
return dynamicPage(pageProperties) {
section("Select the main thermostat") {
input thermostat
}
section("Use remote sensors to adjust the thermostat (by default the app is using the internal sensor of the thermostat)") {
input "remoteSensors", "bool", title: "Enable remote sensor(s)", required: false, defaultValue: false, submitOnChange: true
if (remoteSensors) {
input sensor
paragraph "If multiple sensors are selected, the average temperature is automatically calculated"
}
}
section("When the temperature falls below this temperature (Low Temperature)..."){
input setLow
}
section("Adjust the thermostat to the following settings:"){
input cold
input SetHeatingLow
input SetCoolingLow
}
section("When the temperature raises above this temperature (High Temperature)..."){
input setHigh
}
section("Adjust the thermostat to the following settings:"){
input hot
input SetCoolingHigh
input SetHeatingHigh
}
section("When temperature is neutral (between above Low and High Temperatures..."){
input "neutral", "bool", title: "Turn off the thermostat", required: false, defaultValue: false
}
}
}
// Page - Disabled Mode
def ThermostatandDoors() {
def doors = [
name: "doors",
type: "capability.contactSensor",
title: "Which Sensor(s)?",
multiple: true,
required: false
]
def turnOffDelay = [
name: "turnOffDelay",
type: "decimal",
title: "Number of minutes",
required: false
]
def resetOff = [
name: "resetOff",
type: "bool",
title: "Reset Thermostat Settings when all Sensor(s) are closed",
required: false,
defaultValue: false
]
def pageName = "Thermostat and Doors"
def pageProperties = [
name: "ThermostatandDoors",
title: "",
//nextPage: "ThermostatAway"
]
return dynamicPage(pageProperties) {
section("When one or more contact sensors open, then turn off the thermostat") {
input doors
}
section("Wait this long before turning the thermostat off (defaults to 1 minute)") {
input turnOffDelay
input resetOff
}
}
}
// Page - Thermostat Away
def ThermostatAway() {
def modes2 = [
name: "modes2",
type: "mode",
title: "Which Location Mode(s)?",
multiple: true,
required: false
]
def away = [
name: "away",
type: "enum",
title: "Mode?",
metadata: [values:["auto", "heat", "cool", "off"]],
required: false
]
def setAwayLow = [
name: "setAwayLow",
type: "decimal",
title: "Low temp?",
required: false
]
def AwayCold = [
name: "AwayCold",
type: "enum",
title: "Mode?",
metadata: [values:["auto", "heat", "cool", "off"]],
required: false,
]
def setAwayHigh = [
name: "setAwayHigh",
type: "decimal",
title: "High temp?",
required: false
]
def AwayHot = [
name: "AwayHot",
type: "enum",
title: "Mode?",
required: false,
metadata: [values:["auto", "heat", "cool", "off"]]
]
def SetHeatingAway = [
name: "SetHeatingAway",
type: "number",
title: "Heating Temperature (degrees)",
required: false
]
def SetCoolingAway = [
name: "SetCoolingAway",
type: "number",
title: "Cooling Temperature (degrees)",
required: false
]
def fanAway = [
name: "fanAway",
type: "enum",
title: "Fan Mode?",
metadata: [values:["fanAuto", "fanOn", "fanCirculate"]],
required: false
]
def pageName = "Thermostat Away"
def pageProperties = [
name: "ThermostatAway",
title: "",
//nextPage: "Settings"
]
return dynamicPage(pageProperties) {
section("When the Location Mode changes to 'Away'") {
input modes2
}
section("Adjust the thermostat to the following settings:") {
input away
input fanAway
input SetHeatingAway
input SetCoolingAway
}
section("If the temperature falls below this temperature while away..."){
input setAwayLow
}
section("Automatically adjust the thermostat to the following operating mode..."){
input AwayCold
}
section("If the temperature raises above this temperature while away..."){
input setAwayHigh
}
section("Automatically adjust the thermostat to the following operating mode..."){
input AwayHot
}
}
}
// Show "Setup" page
def Settings() {
def days = [
name: "days",
type: "enum",
title: "Only on certain days of the week",
multiple: true,
required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
]
def modes = [
name: "modes",
type: "mode",
title: "Only when mode is",
multiple: true,
required: false
]
def pageName = ""
def pageProperties = [
name: "Settings",
title: "",
//nextPage: "pageSetup"
]
return dynamicPage(pageProperties) {
section("Notifications") {
input("recipients", "contact", title: "Send notifications to", multiple: true, required: false) {
paragraph "You may enter multiple phone numbers separated by semicolon."+
"E.G. 8045551122;8046663344"
input "sms", "phone", title: "To this phone", multiple: false, required: false
input "push", "bool", title: "Send Push Notification (optional)", required: false, defaultValue: false
}
}
section(title: "Restrictions", hideable: true) {
href "timeIntervalInput", title: "Only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true
input days
input modes
}
section(title: "Debug") {
input "debug", "bool", title: "Enable debug messages in IDE for troubleshooting purposes", required: false, defaultValue: false, refreshAfterSelection:true
input "info", "bool", title: "Enable info messages in IDE to display actions in Live Logging", required: false, defaultValue: false, refreshAfterSelection:true
}
}
}
def installed(){
if (debug) log.debug "Installed called with $settings"
init()
}
def updated(){
if (debug) log.debug "Updated called with $settings"
unsubscribe()
init()
}
def init(){
state.lastStatus = null
runIn(60, "temperatureHandler")
if (debug) log.debug "Temperature will be evaluated in one minute"
if(sensor) {
subscribe(sensor, "temperature", temperatureHandler)
}
else {
subscribe(thermostat, "temperature", temperatureHandler)
}
if(modes2){
subscribe(location, modeAwayChange)
if(sensor) {
subscribe(sensor, "temperature", modeAwayTempHandler)
}
else {
subscribe(thermostat, "temperature", modeAwayTempHandler)
}
}
if(doors){
subscribe(doors, "contact.open", temperatureHandler)
subscribe(doors, "contact.closed", doorCheck)
state.disabledTemp = null
state.disabledMode = null
state.disableHSP = null
state.disableCSP = null
}
}
def temperatureHandler(evt) {
def currentTemp
if(modeOk && daysOk && timeOk && modeNotAwayOk) {
if(sensor){
//def sensors = sensor.size()
//def tempAVG = sensor ? getAverage(sensor, "temperature") : "undefined device"
//currentTemp = tempAVG
currentTemp = sensor.latestValue("temperature")
if (debug) log.debug "Data check (avg temp: ${currentTemp}, num of sensors:${sensors}, app status: ${lastStatus})"
}
else {
currentTemp = thermostat.latestValue("temperature")
if (debug) log.debug "Thermostat data (curr temp: ${currentTemp},status: ${lastStatus}"
}
if(setLow > setHigh){
def temp = setLow
setLow = setHigh
setHigh = temp
if(info) log.info "Detected ${setLow} > ${setHigh}. Auto-adjusting setting to ${temp}"
}
if (doorsOk) {
def currentMode = thermostat.latestValue("thermostatMode")
def currentHSP = thermostat.latestValue("heatingSetpoint")
def currentCSP = thermostat.latestValue("coolingSetpoint")
if (debug) log.debug "App data (curr temp: ${currentTemp},curr mode: ${currentMode}, currentHSP: ${currentHSP},"+
" currentCSP: ${currentCSP}, last status: ${lastStatus}"
if (currentTemp < setLow) {
if (state.lastStatus == "one" || state.lastStatus == "two" || state.lastStatus == "three" || state.lastStatus == null){
state.lastStatus = "one"
if (currentMode == "cool" || currentMode == "off") {
def msg = "Adjusting ${thermostat} operating mode and setpoints because temperature is below ${setLow}"
if (cold) thermostat?."${cold}"()
thermostat?.setHeatingSetpoint(SetHeatingLow)
if (SetCoolingLow) thermostat?.setCoolingSetpoint(SetCoolingLow)
thermostat?.poll()
sendMessage(msg)
if (info) log.info msg
}
else if (currentHSP < SetHeatingLow) {
def msg = "Adjusting ${thermostat} setpoints because temperature is below ${setLow}"
thermostat?.setHeatingSetpoint(SetHeatingLow)
if (SetCoolingLow) thermostat?.setCoolingSetpoint(SetCoolingLow)
thermostat?.poll()
sendMessage(msg)
if (info) log.info msg
}
}
}
if (currentTemp > setHigh) {
if (state.lastStatus == "one" || state.lastStatus == "two" || state.lastStatus == "three" || state.lastStatus == null){
state.lastStatus = "two"
if (currentMode == "heat" || currentMode == "off") {
def msg = "Adjusting ${thermostat} operating mode and setpoints because temperature is above ${setHigh}"
if (hot) thermostat?."${hot}"()
if (SetHeatingHigh) thermostat?.setHeatingSetpoint(SetHeatingHigh)
thermostat?.setCoolingSetpoint(SetCoolingHigh)
thermostat?.poll()
sendMessage(msg)
if (info) log.info msg
}
else if (currentCSP > SetCoolingHigh) {
def msg = "Adjusting ${thermostat} setpoints because temperature is above ${setHigh}"
thermostat?.setCoolingSetpoint(SetCoolingHigh)
if (SetHeatingHigh) thermostat?.setHeatingSetpoint(SetHeatingHigh)
thermostat?.poll()
sendMessage(msg)
if (info) log.info msg
}
}
}
if (currentTemp > setLow && currentTemp < setHigh) {
if (neutral == true) {
if (debug) log.debug "Neutral is ${neutral}, current temp is: ${currentTemp}"
if (state.lastStatus == "two" || state.lastStatus == "one" || state.lastStatus == null){
def msg = "Adjusting ${thermostat} mode to off because temperature is neutral"
thermostat?.off()
thermostat?.poll()
sendMessage(msg)
state.lastStatus = "three"
if (info) log.info msg
if (debug) log.debug "Data check neutral(neutral is:${neutral}, currTemp: ${currentTemp}, setLow: ${setLow}, setHigh: ${setHigh})"
}
}
if (info) log.info "Temperature is neutral not taking action because neutral mode is: ${neutral}"
}
}
else{
def delay = (turnOffDelay != null && turnOffDelay != "") ? turnOffDelay * 60 : 60
if(info) log.info ("Detected open doors. Checking door states again in ${delay} seconds")
runIn(delay, "doorCheck")
}
}
if (debug) log.debug "Temperature handler called: modeOk = $modeOk, daysOk = $daysOk, timeOk = $timeOk, modeNotAwayOk = $modeNotAwayOk "
}
def modeAwayChange(evt){
if(modeOk && daysOk && timeOk){
if (modes2){
if(modes2.contains(location.mode)){
state.lastStatus = "away"
if (away) thermostat."${away}"()
if(SetHeatingAway) thermostat.setHeatingSetpoint(SetHeatingAway)
if(SetCoolingAway) thermostat.setCoolingSetpoint(SetCoolingAway)
if(fanAway) thermostat.setThermostatFanMode(fanAway)
def msg = "Adjusting ${thermostat} mode and setpoints because Location Mode is set to Away"
sendMessage(msg)
if(info) log.info "Running AwayChange because mode is now ${away} and last staus is ${lastStatus}"
}
else {
state.lastStatus = null
temperatureHandler()
if(info) log.info "Running Temperature Handler because Home Mode is no longer in away, and the last staus is ${lastStatus}"
}
}
if(info) log.info ("Detected temperature change while away but all settings are ok, not taking any actions.")
}
}
def modeAwayTempHandler(evt) {
def tempAVGaway = sensor ? getAverage(sensor, "temperature") : "undefined device"
def currentAwayTemp = thermostat.latestValue("temperature")
if(info) log.info "Away: your average room temperature is: ${tempAVGaway}, current temp is ${currentAwayTemp}"
if (sensor) currentAwayTemp = tempAVGaway
if(lastStatus == "away"){
if(modes2.contains(location.mode)){
if (currentAwayTemp < setAwayLow) {
if(Awaycold) thermostat?."${Awaycold}"()
thermostat?.poll()
def msg = "I changed your ${thermostat} mode to ${Awaycold} because temperature is below ${setAwayLow}"
sendMessage(msg)
if (info) log.info msg
}
if (currentAwayTemp > setHigh) {
if(Awayhot) thermostat?."${Awayhot}"()
thermostat?.poll()
def msg = "I changed your ${thermostat} mode to ${Awayhot} because temperature is above ${setAwayHigh}"
sendMessage(msg)
if (info) log.info msg
}
}
else {
state.lastStatus = null
temperatureHandler()
if(info) log.info "Temp changed while staus is ${lastStatus} but the Location Mode is no longer in away. Resetting lastStatus"
}
}
}
def doorCheck(evt){
state.disabledTemp = sensor.latestValue("temperature")
state.disabledMode = thermostat.latestValue("thermostatMode")
state.disableHSP = thermostat.latestValue("heatingSetpoint")
state.disableCSP = thermostat.latestValue("coolingSetpoint")
if (debug) log.debug "Disable settings: ${state.disabledMode} mode, ${state.disableHSP} HSP, ${state.disableCSP} CSP"
if (!doorsOk){
if(info) log.info ("doors still open turning off ${thermostat}")
def msg = "I changed your ${thermostat} mode to off because some doors are open"
if (state.lastStatus != "off"){
thermostat?.off()
sendMessage(msg)
if (info) log.info msg
}
state.lastStatus = "off"
if (info) log.info "Changing status to off"
}
else {
if (state.lastStatus == "off"){
state.lastStatus = null
if (resetOff){
if(debug) log.debug "Contact sensor(s) are now closed restoring ${thermostat} with settings: ${state.disabledMode} mode"+
", ${state.disableHSP} HSP, ${state.disableCSP} CSP"
thermostat."${state.disabledMode}"()
thermostat.setHeatingSetpoint(state.disableHSP)
thermostat.setCoolingSetpoint(state.disableCSP)
}
}
temperatureHandler()
if(debug) "Calling Temperature Handler"
}
}
private getAverage(device,type){
def total = 0
if(debug) log.debug "calculating average temperature"
device.each {total += it.latestValue(type)}
return Math.round(total/device.size())
}
private void sendText(number, message) {
if (sms) {
def phones = sms.split("\\;")
for (phone in phones) {
sendSms(phone, message)
}
}
}
private void sendMessage(message) {
if(info) log.info "sending notification: ${message}"
if (recipients) {
sendNotificationToContacts(message, recipients)
if(debug) log.debug "sending notification: ${recipients}"
}
if (push) {
sendPush message
if(info) log.info "sending push notification"
} else {
sendNotificationEvent(message)
if(info) log.info "sending notification"
}
if (sms) {
sendText(sms, message)
if(debug) "Calling process to send text"
}
}
private getAllOk() {
modeOk && daysOk && timeOk && doorsOk && modeNotAwayOk
}
private getModeOk() {
def result = !modes || modes.contains(location.mode)
if(debug) log.debug "modeOk = $result"
result
}
private getModeNotAwayOk() {
def result = !modes2 || !modes2.contains(location.mode)
if(debug) log.debug "modeNotAwayOk = $result"
result
}
private getDoorsOk() {
def result = !doors || !doors.latestValue("contact").contains("open")
if(debug) log.debug "doorsOk = $result"
result
}
private getDaysOk() {
def result = true
if (days) {
def df = new java.text.SimpleDateFormat("EEEE")
if (location.timeZone) {
df.setTimeZone(location.timeZone)
}
else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
}
def day = df.format(new Date())
result = days.contains(day)
}
if(debug) log.debug "daysOk = $result"
result
}
private getTimeOk() {
def result = true
if (starting && ending) {
def currTime = now()
def start = timeToday(starting).time
def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
else if (starting){
result = currTime >= start
}
else if (ending){
result = currTime <= stop
}
if(debug) log.debug "timeOk = $result"
result
}
def getTimeLabel(starting, ending){
def timeLabel = "Tap to set"
if(starting && ending){
timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
}
else if (starting) {
timeLabel = "Start at" + " " + hhmm(starting)
}
else if(ending){
timeLabel = "End at" + hhmm(ending)
}
timeLabel
}
private hhmm(time, fmt = "h:mm a")
{
def t = timeToday(time, location.timeZone)
def f = new java.text.SimpleDateFormat(fmt)
f.setTimeZone(location.timeZone ?: timeZone(time))
f.format(t)
}
def greyedOut(){
def result = ""
if (sensor) {
result = "complete"
}
result
}
def greyedOutTherm(){
def result = ""
if (thermostat) {
result = "complete"
}
result
}
def greyedOutTherm2(){
def result = ""
if (modes2) {
result = "complete"
}
result
}
def greyedOutSettings(){
def result = ""
if (starting || ending || days || modes || push) {
result = "complete"
}
result
}
def greyedOutTime(starting, ending){
def result = ""
if (starting || ending) {
result = "complete"
}
result
}
private anyoneIsHome() {
def result = false
if(people.findAll { it?.currentPresence == "present" }) {
result = true
}
if(debug) log.debug("anyoneIsHome: ${result}")
return result
}
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
section {
input "starting", "time", title: "Starting (both are required)", required: false
input "ending", "time", title: "Ending (both are required)", required: false
}
}

View File

@@ -0,0 +1,423 @@
definition(
name: 'Lock Manager',
namespace: 'ethayer',
author: 'Erik Thayer',
description: 'Manage locks and users',
category: 'Safety & Security',
iconUrl: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/lm.jpg',
iconX2Url: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/lm2x.jpg',
iconX3Url: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/lm3x.jpg'
)
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
preferences {
page name: 'mainPage', title: 'Installed', install: true, uninstall: true, submitOnChange: true
page name: 'infoRefreshPage'
page name: 'notificationPage'
page name: 'helloHomePage'
page name: 'lockInfoPage'
page name: 'keypadPage'
page name: 'askAlexaPage'
}
def mainPage() {
dynamicPage(name: 'mainPage', install: true, uninstall: true, submitOnChange: true) {
section('Create') {
app(name: 'locks', appName: 'Lock', namespace: 'ethayer', title: 'New Lock', multiple: true, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/new-lock.png')
app(name: 'lockUsers', appName: 'Lock User', namespace: 'ethayer', title: 'New User', multiple: true, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/user-plus.png')
app(name: 'keypads', appName: 'Keypad', namespace: 'ethayer', title: 'New Keypad', multiple: true, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/keypad-plus.png')
}
section('Locks') {
def lockApps = getLockApps()
lockApps = lockApps.sort{ it.lock.id }
if (lockApps) {
def i = 0
lockApps.each { lockApp ->
i++
href(name: "toLockInfoPage${i}", page: 'lockInfoPage', params: [id: lockApp.lock.id], required: false, title: lockApp.label, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/lock.png' )
}
}
}
section('Global Settings') {
href(name: 'toNotificationPage', page: 'notificationPage', title: 'Notification Settings', description: notificationPageDescription(), state: notificationPageDescription() ? 'complete' : '', image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/bullhorn.png')
def actions = location.helloHome?.getPhrases()*.label
if (actions) {
href(name: 'toHelloHomePage', page: 'helloHomePage', title: 'Hello Home Settings', image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/home.png')
}
def keypadApps = getKeypadApps()
if (keypadApps) {
href(name: 'toKeypadPage', page: 'keypadPage', title: 'Keypad Routines (optional)', image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/keypad.png')
}
}
section('Advanced', hideable: true, hidden: true) {
input(name: 'overwriteMode', title: 'Overwrite?', type: 'bool', required: true, defaultValue: true, description: 'Overwrite mode automatically deletes codes not in the users list')
input(name: 'enableDebug', title: 'Enable IDE debug messages?', type: 'bool', required: true, defaultValue: false, description: 'Show activity from Lock Manger in logs for debugging.')
paragraph 'Lock Manager © 2017 v1.4'
}
}
}
def lockInfoPage(params) {
dynamicPage(name:"lockInfoPage", title:"Lock Info") {
def lockApp = getLockAppByIndex(params)
if (lockApp) {
section("${lockApp.label}") {
def complete = lockApp.isCodeComplete()
def refreshComplete = lockApp.isRefreshComplete()
if (!complete) {
paragraph 'App is learning codes. They will appear here when received.\n Lock may require special DTH to work properly'
lockApp.lock.poll()
}
if (!refreshComplete) {
paragraph 'App is in refresh mode.'
}
def codeData = lockApp.codeData()
if (codeData) {
def setCode = ''
def usage
def para
def image
def sortedCodes = codeData.sort{it.value.slot}
sortedCodes.each { data ->
data = data.value
if (data.codeState != 'unknown') {
def userApp = lockApp.findSlotUserApp(data.slot)
para = "Slot ${data.slot}"
if (data.code) {
para = para + "\nCode: ${data.code}"
}
if (userApp) {
para = para + userApp.getLockUserInfo(lockApp.lock)
image = userApp.lockInfoPageImage(lockApp.lock)
} else {
image = 'https://dl.dropboxusercontent.com/u/54190708/LockManager/times-circle-o.png'
}
if (data.codeState == 'refresh') {
para = para +'\nPending refresh...'
}
paragraph para, image: image
}
}
}
}
section('Lock Settings') {
def pinLength = lockApp.pinLength()
if (pinLength) {
paragraph "Required Length: ${pinLength}"
}
}
} else {
section() {
paragraph 'Error: Can\'t find lock!'
}
}
}
}
def notificationPage() {
dynamicPage(name: 'notificationPage', title: 'Global Notification Settings') {
section {
paragraph 'These settings will apply to all users. Settings on individual users will override these settings'
input('recipients', 'contact', title: 'Send notifications to', submitOnChange: true, required: false, multiple: true, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/book.png')
href(name: 'toAskAlexaPage', title: 'Ask Alexa', page: 'askAlexaPage', image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/Alexa.png')
if (!recipients) {
input(name: 'phone', type: 'text', title: 'Text This Number', description: 'Phone number', required: false, submitOnChange: true)
paragraph 'For multiple SMS recipients, separate phone numbers with a semicolon(;)'
input(name: 'notification', type: 'bool', title: 'Send A Push Notification', description: 'Notification', required: false, submitOnChange: true)
}
if (phone != null || notification || recipients) {
input(name: 'notifyAccess', title: 'on User Entry', type: 'bool', required: false, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/unlock-alt.png')
input(name: 'notifyLock', title: 'on Lock', type: 'bool', required: false, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/lock.png')
input(name: 'notifyAccessStart', title: 'when granting access', type: 'bool', required: false, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/check-circle-o.png')
input(name: 'notifyAccessEnd', title: 'when revoking access', type: 'bool', required: false, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/times-circle-o.png')
}
}
section('Only During These Times (optional)') {
input(name: 'notificationStartTime', type: 'time', title: 'Notify Starting At This Time', description: null, required: false)
input(name: 'notificationEndTime', type: 'time', title: 'Notify Ending At This Time', description: null, required: false)
}
}
}
def helloHomePage() {
dynamicPage(name: 'helloHomePage', title: 'Global Hello Home Settings (optional)') {
def actions = location.helloHome?.getPhrases()*.label
actions?.sort()
section('Hello Home Phrases') {
input(name: 'manualUnlockRoutine', title: 'On Manual Unlock', type: 'enum', options: actions, required: false, multiple: true, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/unlock-alt.png')
input(name: 'manualLockRoutine', title: 'On Manual Lock', type: 'enum', options: actions, required: false, multiple: true, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/lock.png')
input(name: 'codeUnlockRoutine', title: 'On Code Unlock', type: 'enum', options: actions, required: false, multiple: true, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/unlock-alt.png' )
paragraph 'Supported on some locks:'
input(name: 'codeLockRoutine', title: 'On Code Lock', type: 'enum', options: actions, required: false, multiple: true, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/lock.png')
paragraph 'These restrictions apply to all the above:'
input "userNoRunPresence", "capability.presenceSensor", title: "DO NOT run Actions if any of these are present:", multiple: true, required: false
input "userDoRunPresence", "capability.presenceSensor", title: "ONLY run Actions if any of these are present:", multiple: true, required: false
}
}
}
def askAlexaPage() {
dynamicPage(name: 'askAlexaPage', title: 'Ask Alexa Message Settings') {
section('Que Messages with the Ask Alexa app') {
paragraph 'These settings apply to all users. These settings are overridable on the user level'
input(name: 'alexaAccess', title: 'on User Entry', type: 'bool', required: false, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/unlock-alt.png')
input(name: 'alexaLock', title: 'on Lock', type: 'bool', required: false, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/lock.png')
input(name: 'alexaAccessStart', title: 'when granting access', type: 'bool', required: false, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/check-circle-o.png')
input(name: 'alexaAccessEnd', title: 'when revoking access', type: 'bool', required: false, image: 'https://dl.dropboxusercontent.com/u/54190708/LockManager/times-circle-o.png')
}
section('Only During These Times (optional)') {
input(name: 'alexaStartTime', type: 'time', title: 'Notify Starting At This Time', description: null, required: false)
input(name: 'alexaEndTime', type: 'time', title: 'Notify Ending At This Time', description: null, required: false)
}
}
}
def keypadPage() {
dynamicPage(name: 'keypadPage',title: 'Keypad Settings (optional)', install: true, uninstall: true) {
def actions = location.helloHome?.getPhrases()*.label
actions?.sort()
section("Settings") {
paragraph 'settings here are for all users. When any user enters their passcode, run these routines'
input(name: 'armRoutine', title: 'Arm/Away routine', type: 'enum', options: actions, required: false, multiple: true)
input(name: 'disarmRoutine', title: 'Disarm routine', type: 'enum', options: actions, required: false, multiple: true)
input(name: 'stayRoutine', title: 'Arm/Stay routine', type: 'enum', options: actions, required: false, multiple: true)
input(name: 'nightRoutine', title: 'Arm/Night routine', type: 'enum', options: actions, required: false, multiple: true)
}
}
}
def fancyString(listOfStrings) {
listOfStrings.removeAll([null])
def fancify = { list ->
return list.collect {
def label = it
if (list.size() > 1 && it == list[-1]) {
label = "and ${label}"
}
label
}.join(", ")
}
return fancify(listOfStrings)
}
def notificationPageDescription() {
def parts = []
def msg = ""
if (settings.phone) {
parts << "SMS to ${phone}"
}
if (settings.recipients) {
parts << 'Sent to Address Book'
}
if (settings.notification) {
parts << 'Push Notification'
}
msg += fancyString(parts)
parts = []
if (settings.notifyAccess) {
parts << 'on entry'
}
if (settings.notifyLock) {
parts << 'on lock'
}
if (settings.notifyAccessStart) {
parts << 'when granting access'
}
if (settings.notifyAccessEnd) {
parts << 'when revoking access'
}
if (settings.notificationStartTime) {
parts << "starting at ${settings.notificationStartTime}"
}
if (settings.notificationEndTime) {
parts << "ending at ${settings.notificationEndTime}"
}
if (parts.size()) {
msg += ': '
msg += fancyString(parts)
}
return msg
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
def children = getChildApps()
log.debug "there are ${children.size()} lock users"
}
def getLockAppByIndex(params) {
def id = ''
// Assign params to id. Sometimes parameters are double nested.
if (params.id) {
id = params.id
} else if (params.params){
id = params.params.id
} else if (state.lastLock) {
id = state.lastLock
}
state.lastLock = id
def lockApp = false
def lockApps = getLockApps()
if (lockApps) {
def i = 0
lockApps.each { app ->
if (app.lock.id == state.lastLock) {
lockApp = app
}
}
}
return lockApp
}
def availableSlots(selectedSlot) {
def options = []
(1..30).each { slot->
def children = getChildApps()
def available = true
children.each { child ->
def userSlot = child.userSlot
if (!selectedSlot) {
selectedSlot = 0
}
if (!userSlot) {
userSlot = 0
}
if (userSlot.toInteger() == slot && selectedSlot.toInteger() != slot) {
available = false
}
}
if (available) {
options << ["${slot}": "Slot ${slot}"]
}
}
return options
}
def keypadMatchingUser(usedCode){
def correctUser = false
def userApps = getUserApps()
userApps.each { userApp ->
def code
log.debug userApp.userCode
if (userApp.isActiveKeypad()) {
code = userApp.userCode.take(4)
log.debug "code: ${code} used: ${usedCode}"
if (code.toInteger() == usedCode.toInteger()) {
correctUser = userApp
}
}
}
return correctUser
}
def findAssignedChildApp(lock, slot) {
def childApp
def userApps = getUserApps()
userApps.each { child ->
if (child.userSlot?.toInteger() == slot) {
childApp = child
}
}
return childApp
}
def getUserApps() {
def userApps = []
def children = getChildApps()
children.each { child ->
if (child.userSlot) {
userApps.push(child)
}
}
return userApps
}
def getKeypadApps() {
def keypadApps = []
def children = getChildApps()
children.each { child ->
if (child.keypad) {
keypadApps.push(child)
}
}
return keypadApps
}
def getLockApps() {
def lockApps = []
def children = getChildApps()
children.each { child ->
if (child.lock) {
lockApps.push(child)
}
}
return lockApps
}
def setAccess() {
def lockApps = getLockApps()
lockApps.each { lockApp ->
lockApp.makeRequest()
}
}
def debuggerOn() {
// needed for child apps
return enableDebug
}
def debugger(message) {
def doDebugger = debuggerOn()
if (enableDebug) {
return log.debug(message)
}
}
def anyoneHome(sensors) {
def result = false
if(sensors.findAll { it?.currentPresence == "present" }) {
result = true
}
result
}
def executeHelloPresenceCheck(routines) {
if (userNoRunPresence && userDoRunPresence == null) {
if (!anyoneHome(userNoRunPresence)) {
location.helloHome.execute(routines)
}
} else if (userDoRunPresence && userNoRunPresence == null) {
if (anyoneHome(userDoRunPresence)) {
location.helloHome.execute(routines)
}
} else if (userDoRunPresence && userNoRunPresence) {
if (anyoneHome(userDoRunPresence) && !anyoneHome(userNoRunPresence)) {
location.helloHome.execute(routines)
}
} else {
location.helloHome.execute(routines)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -98,7 +98,7 @@ def motionHandler(evt) {
else {
state.motionStopTime = now()
if(delayMinutes) {
runIn(delayMinutes*60, turnOffMotionAfterDelay, [overwrite: true])
runIn(delayMinutes*60, turnOffMotionAfterDelay, [overwrite: false])
} else {
turnOffMotionAfterDelay()
}

View File

@@ -125,19 +125,19 @@
if(allOk) {
if(everyoneIsAway() && (state.sunMode == "sunrise")) {
log.debug("Home is Empty Setting New Away Mode")
log.info("Home is Empty Setting New Away Mode")
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
runIn(delay, "setAway")
}
if(everyoneIsAway() && (state.sunMode == "sunset")) {
log.debug("Home is Empty Setting New Away Mode")
log.info("Home is Empty Setting New Away Mode")
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
runIn(delay, "setAway")
}
else {
log.debug("Home is Occupied Setting New Home Mode")
log.info("Home is Occupied Setting New Home Mode")
setHome()
@@ -152,7 +152,7 @@
log.debug("Checking if everyone is away")
if(everyoneIsAway()) {
log.debug("Nobody is home, running away sequence")
log.info("Nobody is home, running away sequence")
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
runIn(delay, "setAway")
}
@@ -161,7 +161,7 @@
else {
def lastTime = state[evt.deviceId]
if (lastTime == null || now() - lastTime >= 1 * 60000) {
log.debug("Someone is home, running home sequence")
log.info("Someone is home, running home sequence")
setHome()
}
state[evt.deviceId] = now()
@@ -175,14 +175,14 @@
if(everyoneIsAway()) {
if(state.sunMode == "sunset") {
def message = "Performing \"${awayNight}\" for you as requested."
log.debug(message)
log.info(message)
sendAway(message)
location.helloHome.execute(settings.awayNight)
}
else if(state.sunMode == "sunrise") {
def message = "Performing \"${awayDay}\" for you as requested."
log.debug(message)
log.info(message)
sendAway(message)
location.helloHome.execute(settings.awayDay)
}
@@ -192,19 +192,19 @@
}
else {
log.debug("Somebody returned home before we set to '${newAwayMode}'")
log.info("Somebody returned home before we set to '${newAwayMode}'")
}
}
//set home mode when house is occupied
def setHome() {
sendOutOfDateNotification()
log.debug("Setting Home Mode!!")
log.info("Setting Home Mode!!")
if(anyoneIsHome()) {
if(state.sunMode == "sunset"){
if (location.mode != "${homeModeNight}"){
def message = "Performing \"${homeNight}\" for you as requested."
log.debug(message)
log.info(message)
sendHome(message)
location.helloHome.execute(settings.homeNight)
}
@@ -213,7 +213,7 @@
if(state.sunMode == "sunrise"){
if (location.mode != "${homeModeDay}"){
def message = "Performing \"${homeDay}\" for you as requested."
log.debug(message)
log.info(message)
sendHome(message)
location.helloHome.execute(settings.homeDay)
}
@@ -329,4 +329,4 @@
sendNotification("Your version of Hello, Home Phrase Director is currently out of date. Please look for the new version of Hello, Home Phrase Director now called 'Routine Director' in the marketplace.")
state.lastTime = (new Date() + 31).getTime()
}
}
}