mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-28 21:04:16 +00:00
Compare commits
15 Commits
MSA-1969-1
...
MSA-1987-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cab4456443 | ||
|
|
01c2968f91 | ||
|
|
40b75ce665 | ||
|
|
9b01a7d8be | ||
|
|
12bb6c0492 | ||
|
|
7cf8bb1917 | ||
|
|
d4fd778a64 | ||
|
|
eb870e5f31 | ||
|
|
d60657e466 | ||
|
|
d8dc70ae9e | ||
|
|
3457bbad42 | ||
|
|
c6c4b09fbb | ||
|
|
7e25d32c70 | ||
|
|
abc5683ed3 | ||
|
|
1d629cfc9a |
@@ -1,212 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2017 Ian Lindsay
|
|
||||||
*
|
|
||||||
* Code snippets taken from Tim Slagle
|
|
||||||
* https://community.smartthings.com/u/tslagle13
|
|
||||||
* here:
|
|
||||||
* https://community.smartthings.com/t/generic-camera-device-using-local-connection-new-version-now-available/3269/75?u=l0kiscot
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
|
||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing permissions and limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
input("devicekey", "text", title: "Device Key", description: "Your OpenGarage.io device key")
|
|
||||||
input("ipadd", "text", title: "IP address", description: "The IP address of your OpenGarage.io unit")
|
|
||||||
input("port", "text", title: "Port", description: "The port of your OpenGarage.io unit")
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "OpenGarage.io Handler", namespace: "littlegumSmartHome", author: "Ian Lindsay") {
|
|
||||||
capability "Door Control"
|
|
||||||
capability "Garage Door Control"
|
|
||||||
capability "Refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles (scale: 2){
|
|
||||||
standardTile("garagedoor", "device.garagedoor", width: 6, height: 4) {
|
|
||||||
state "open", label: '${name}', action: "close", icon: "st.doors.garage.garage-open", backgroundColor: "#e54444"
|
|
||||||
state "closed", label: '${name}', action: "open", icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821"
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 6, height: 2) {
|
|
||||||
state "default", action: "refresh.refresh", icon: "st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main("garagedoor")
|
|
||||||
details(["garagedoor", "refresh"])
|
|
||||||
}
|
|
||||||
simulator {
|
|
||||||
// simulator metadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
log.debug "initialize triggered"
|
|
||||||
// initialize state
|
|
||||||
state.doorStatus = 1 // 1 means open, 0 means closed
|
|
||||||
api("getstatus", [])
|
|
||||||
}
|
|
||||||
|
|
||||||
def open() {
|
|
||||||
log.debug "Executing 'close'"
|
|
||||||
api("openclose", [])
|
|
||||||
}
|
|
||||||
|
|
||||||
def close() {
|
|
||||||
log.debug "Executing 'close'"
|
|
||||||
api("openclose", [])
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "Refreshing Values "
|
|
||||||
|
|
||||||
api("getstatus", [])
|
|
||||||
}
|
|
||||||
|
|
||||||
def api(method, args = [], success = {}) {
|
|
||||||
def methods = [
|
|
||||||
"getstatus": [gdipadd: "${ipadd}", gdport:"${port}", gdpath:"/jc", gdtype: "get"],
|
|
||||||
"openclose": [gdipadd: "${ipadd}", gdport:"${port}", gdpath:"/cc?dkey=${devicekey}&click=1", gdtype: "get"]
|
|
||||||
]
|
|
||||||
|
|
||||||
def request = methods.getAt(method)
|
|
||||||
|
|
||||||
doRequest(request.gdipadd, request.gdport, request.gdpath, request.gdtype, success)
|
|
||||||
}
|
|
||||||
|
|
||||||
private doRequest(gdipadd, gdport, gdpath, gdtype, success) {
|
|
||||||
log.debug(gdipadd)
|
|
||||||
|
|
||||||
//if(type == "post") {
|
|
||||||
// httpPost(uri , "", success)
|
|
||||||
//}
|
|
||||||
|
|
||||||
//else if(type == "get") {
|
|
||||||
// httpGet(uri, success)
|
|
||||||
//}
|
|
||||||
|
|
||||||
def host = gdipadd
|
|
||||||
def hosthex = convertIPToHex(host)
|
|
||||||
def porthex = Long.toHexString(Long.parseLong((gdport)))
|
|
||||||
if (porthex.length() < 4) { porthex = "00" + porthex }
|
|
||||||
|
|
||||||
//log.debug "Port in Hex is $porthex"
|
|
||||||
//log.debug "Hosthex is : $hosthex"
|
|
||||||
device.deviceNetworkId = "$hosthex:$porthex"
|
|
||||||
|
|
||||||
//log.debug "The device id configured is: $device.deviceNetworkId"
|
|
||||||
|
|
||||||
//def path = gdpath //"/SnapshotJPEG?Resolution=640x480&Quality=Clarity"
|
|
||||||
log.debug "path is: $gdpath"
|
|
||||||
|
|
||||||
def headers = [:] //"HOST:" + getHostAddress() + ""
|
|
||||||
headers.put("HOST", "$host:$gdport")
|
|
||||||
|
|
||||||
try {
|
|
||||||
def hubAction = new physicalgraph.device.HubAction(
|
|
||||||
method: method,
|
|
||||||
path: gdpath,
|
|
||||||
headers: headers
|
|
||||||
)
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
log.debug "Hit Exception on $hubAction"
|
|
||||||
log.debug e
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(description) {
|
|
||||||
|
|
||||||
def msg = parseLanMessage(description)
|
|
||||||
|
|
||||||
log.debug msg
|
|
||||||
|
|
||||||
def headersAsString = msg.header // => headers as a string
|
|
||||||
def headerMap = msg.headers // => headers as a Map
|
|
||||||
def body = msg.body // => request body as a string
|
|
||||||
def status = msg.status // => http status code of the response
|
|
||||||
//def json = msg.json // => any JSON included in response body, as a data structure of lists and maps
|
|
||||||
//def xml = msg.xml // => any XML included in response body, as a document tree structure
|
|
||||||
//def data = msg.data // => either JSON or XML in response body (whichever is specified by content-type header in response)
|
|
||||||
|
|
||||||
def slurper = new groovy.json.JsonSlurper()
|
|
||||||
def json = slurper.parseText(msg.body)
|
|
||||||
|
|
||||||
log.debug json
|
|
||||||
|
|
||||||
def result
|
|
||||||
log.debug "before state.doorStatus: $state.doorStatus"
|
|
||||||
|
|
||||||
// open / close event
|
|
||||||
if(json.result){
|
|
||||||
if(state.doorStatus){
|
|
||||||
log.debug "door open - so closing"
|
|
||||||
state.doorStatus = 0
|
|
||||||
result = createEvent(name: "garagedoor", value: "closed")
|
|
||||||
} else {
|
|
||||||
log.debug "door closed - so opening"
|
|
||||||
state.doorStatus = 1
|
|
||||||
result = createEvent(name: "garagedoor", value: "open")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//status update request
|
|
||||||
if(json.mac){
|
|
||||||
if(json.door){
|
|
||||||
log.debug "door is open - refreshing setting"
|
|
||||||
state.doorStatus = 1
|
|
||||||
result = createEvent(name: "garagedoor", value: "open")
|
|
||||||
} else {
|
|
||||||
log.debug "door is closed - refreshing setting"
|
|
||||||
state.doorStatus = 0
|
|
||||||
result = createEvent(name: "garagedoor", value: "closed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug "after state.doorStatus: $state.doorStatus"
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long converIntToLong(ipAddress) {
|
|
||||||
long result = 0
|
|
||||||
def parts = ipAddress.split("\\.")
|
|
||||||
for (int i = 3; i >= 0; i--) {
|
|
||||||
result |= (Long.parseLong(parts[3 - i]) << (i * 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result & 0xFFFFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String convertIPToHex(ipAddress) {
|
|
||||||
return Long.toHexString(converIntToLong(ipAddress));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer convertHexToInt(hex) {
|
|
||||||
Integer.parseInt(hex,16)
|
|
||||||
}
|
|
||||||
private String convertHexToIP(hex) {
|
|
||||||
log.debug("Convert hex to ip: $hex") // a0 00 01 6
|
|
||||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
|
||||||
}
|
|
||||||
|
|
||||||
private getHostAddress() {
|
|
||||||
def parts = device.deviceNetworkId.split(":")
|
|
||||||
log.debug device.deviceNetworkId
|
|
||||||
def ip = convertHexToIP(parts[0])
|
|
||||||
def port = convertHexToInt(parts[1])
|
|
||||||
return ip + ":" + port
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ Cloud Execution
|
|||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
* [FortrezZ Siren Strobe Alarm](https://www.smartthings.com/works-with-smartthings/other/fortrezz-water-valve)
|
* [FortrezZ Siren Strobe Alarm](https://www.smartthings.com/products/fortrezz-siren-strobe-alarm)
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ metadata {
|
|||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
|
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, 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, 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, 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"
|
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"
|
||||||
|
|||||||
2
devicetypes/smartthings/zwave-water-valve.src/.st-ignore
Normal file
2
devicetypes/smartthings/zwave-water-valve.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.st-ignore
|
||||||
|
README.md
|
||||||
38
devicetypes/smartthings/zwave-water-valve.src/README.md
Normal file
38
devicetypes/smartthings/zwave-water-valve.src/README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# 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,12 +14,14 @@
|
|||||||
metadata {
|
metadata {
|
||||||
definition (name: "Z-Wave Water Valve", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Z-Wave Water Valve", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
|
capability "Health Check"
|
||||||
capability "Valve"
|
capability "Valve"
|
||||||
capability "Polling"
|
capability "Polling"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
fingerprint deviceId: "0x1006", inClusters: "0x25"
|
fingerprint deviceId: "0x1006", inClusters: "0x25"
|
||||||
|
fingerprint mfr:"0173", prod:"0003", model:"0002", deviceJoinName: "Leak Intelligence Leak Gopher Water Shutoff Valve"
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -53,7 +55,14 @@ 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() {
|
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())
|
response(refresh())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +123,13 @@ def poll() {
|
|||||||
zwave.switchBinaryV1.switchBinaryGet().format()
|
zwave.switchBinaryV1.switchBinaryGet().format()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PING is used by Device-Watch in attempt to reach the Device
|
||||||
|
* */
|
||||||
|
def ping() {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "refresh() is called"
|
log.debug "refresh() is called"
|
||||||
def commands = [zwave.switchBinaryV1.switchBinaryGet().format()]
|
def commands = [zwave.switchBinaryV1.switchBinaryGet().format()]
|
||||||
|
|||||||
423
smartapps/ethayer/lock-manager.src/lock-manager.groovy
Normal file
423
smartapps/ethayer/lock-manager.src/lock-manager.groovy
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
1532
smartapps/ethayer/user-lock-manager.src/user-lock-manager.groovy
Normal file
1532
smartapps/ethayer/user-lock-manager.src/user-lock-manager.groovy
Normal file
File diff suppressed because it is too large
Load Diff
@@ -765,7 +765,6 @@ def turnOffSwitch() {
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
device.off();
|
device.off();
|
||||||
|
|
||||||
return [Device_id: params.id, result_action: "200"]
|
return [Device_id: params.id, result_action: "200"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -789,6 +788,7 @@ def getTempSensorsStatus(id) {
|
|||||||
return []
|
return []
|
||||||
} else {
|
} else {
|
||||||
def bat = getBatteryStatus(device.id)
|
def bat = getBatteryStatus(device.id)
|
||||||
return [temperature: device.currentValue('temperature')] + bat
|
def scale = [Scale: location.temperatureScale]
|
||||||
|
return [temperature: device.currentValue('temperature')] + bat + scale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,6 +162,17 @@ def registerAllDeviceSubscriptions() {
|
|||||||
registerChangeHandler(inputs)
|
registerChangeHandler(inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Subscribe to events from a list of devices
|
||||||
|
def registerChangeHandler(myList) {
|
||||||
|
myList.each { myDevice ->
|
||||||
|
def theAtts = myDevice.supportedAttributes
|
||||||
|
theAtts.each { att ->
|
||||||
|
subscribe(myDevice, att.name, deviceEventHandler)
|
||||||
|
log.info "Registering for ${myDevice.displayName}.${att.name}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Endpoints function: Subscribe to events from a specific device
|
//Endpoints function: Subscribe to events from a specific device
|
||||||
def registerDeviceChange() {
|
def registerDeviceChange() {
|
||||||
def subscriptionEndpt = params.subscriptionURL
|
def subscriptionEndpt = params.subscriptionURL
|
||||||
|
|||||||
Reference in New Issue
Block a user