mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-25 21:04:10 +00:00
Compare commits
3 Commits
MSA-1985-1
...
MSA-1987-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cab4456443 | ||
|
|
01c2968f91 | ||
|
|
12bb6c0492 |
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
@@ -1,373 +0,0 @@
|
|||||||
/**
|
|
||||||
* SmartThingsToStart REST Api
|
|
||||||
*
|
|
||||||
* Copyright 2017 Dr1rrb
|
|
||||||
*
|
|
||||||
* 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: "SmartThingsToStart",
|
|
||||||
namespace: "torick.net",
|
|
||||||
author: "Dr1rrb",
|
|
||||||
description: "SmartThingsToStart REST Api",
|
|
||||||
category: "My Apps",
|
|
||||||
iconUrl: "http://smartthingstostartproxy.azurewebsites.net/Assets/AppLogo.png",
|
|
||||||
iconX2Url: "http://smartthingstostartproxy.azurewebsites.net/Assets/AppLogo@2X.png",
|
|
||||||
iconX3Url: "http://smartthingstostartproxy.azurewebsites.net/Assets/AppLogo@3X.png",
|
|
||||||
oauth: true)
|
|
||||||
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
section("Control these devices") {
|
|
||||||
input "switches", "capability.switch", title: "Select switches", multiple: true, required: false
|
|
||||||
input "bubls", "capability.bulb", title: "Select bubls", hideWhenEmpty: true, multiple: true, required: false
|
|
||||||
input "lights", "capability.light", title: "Select lights", hideWhenEmpty: true, multiple: true, required: false
|
|
||||||
input "outlets", "capability.outlet", title: "Select outlets", hideWhenEmpty: true, multiple: true, required: false
|
|
||||||
input "relaySwitches", "capability.relaySwitch", title: "Select relay switches", hideWhenEmpty: true, multiple: true, required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mappings {
|
|
||||||
path("/infos") {
|
|
||||||
action: [GET: "retreiveServerInfos"]
|
|
||||||
}
|
|
||||||
path("/items") {
|
|
||||||
action: [GET: "retreiveDevicesAndRoutines"]
|
|
||||||
}
|
|
||||||
path("/device/:id") {
|
|
||||||
action: [GET: "retreiveDevice"]
|
|
||||||
}
|
|
||||||
path("/device/:id/subscription/:subscriptionId") {
|
|
||||||
action: [
|
|
||||||
PUT: "updateOrCreateSubscription",
|
|
||||||
POST: "updateOrCreateSubscription",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
// TODO
|
|
||||||
//path("/device/:id/unsubscribe") {
|
|
||||||
// action: [POST: "unsubscribeFromDevice"]
|
|
||||||
//}
|
|
||||||
path("/device/:id/:command") {
|
|
||||||
action: [ PUT: "updateDevice" ]
|
|
||||||
}
|
|
||||||
path("/routine/:id/execute") {
|
|
||||||
action: [PUT: "executeRoutine"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Region: App lifecycle
|
|
||||||
def installed() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated with settings: ${settings}"
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
//state.pushChannels = [:]
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
def channels = state.pushChannels = state.pushChannels ?: [:];
|
|
||||||
channels.each
|
|
||||||
{
|
|
||||||
def device = findDevice(it.key);
|
|
||||||
if (device != null)
|
|
||||||
{
|
|
||||||
subscribeToDevice(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Region: Http request handlers
|
|
||||||
def retreiveServerInfos()
|
|
||||||
{
|
|
||||||
return [version: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
def retreiveDevicesAndRoutines() {
|
|
||||||
def details = params.details == "true" ? true : false;
|
|
||||||
|
|
||||||
return [
|
|
||||||
devices: getDevices().collect { getDeviceInfos(it, details) },
|
|
||||||
routines: location.helloHome?.getPhrases().collect { getRoutineInfos(it, details) }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
def retreiveDevice() {
|
|
||||||
def device = getDevice(params.id);
|
|
||||||
def details = params.details == "true" ? true : false;
|
|
||||||
|
|
||||||
return getDeviceInfos(device, details);
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateOrCreateSubscription() {
|
|
||||||
def device = getDevice(params.id);
|
|
||||||
def channelUri = notNull("channelUri", request.JSON?.channelUri);
|
|
||||||
def token = notNull("token", request.JSON?.token);
|
|
||||||
|
|
||||||
log.debug "Subscribing to device '${device.id}' (target: '${channelUri}' / token: '${token}')"
|
|
||||||
|
|
||||||
// Get or create the push notification channel from / into the local state
|
|
||||||
def subscriptionId = params.subscriptionId ?: UUID.randomUUID().toString() ;
|
|
||||||
def allSubscriptions = state.pushChannels ?: (state.pushChannels = [:]);
|
|
||||||
def deviceSubscriptions = allSubscriptions[device.id] ?: (allSubscriptions[device.id] = []);
|
|
||||||
def subscription = deviceSubscriptions.find { it.id == subscriptionId };
|
|
||||||
if (subscription == null)
|
|
||||||
{
|
|
||||||
deviceSubscriptions << [
|
|
||||||
id: subscriptionId,
|
|
||||||
deviceId: device.id,
|
|
||||||
channelUri: channelUri,
|
|
||||||
token: token
|
|
||||||
];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
subscription["channelUri"] = channelUri;
|
|
||||||
subscription["token"] = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug "Active subscriptions: \n" + state.pushChannels.collect { "** Device ${it.key} **\n" + it.value.collect{c -> "- - - > ${c.channelUri} : ${c.token.substring(0, 10)}..."}.join('\n') + "\n***************************" }.join('\n\n')
|
|
||||||
|
|
||||||
// (Re)create the subscription(s)
|
|
||||||
subscribeToDevice(device)
|
|
||||||
|
|
||||||
return [subscriptionId: subscriptionId];
|
|
||||||
}
|
|
||||||
|
|
||||||
def subscribeToDevice(device)
|
|
||||||
{
|
|
||||||
log.debug "Subscribing to device '${device.id}'"
|
|
||||||
|
|
||||||
unsubscribe(device);
|
|
||||||
subscribe(device, "switch", switchStateChanged)
|
|
||||||
|
|
||||||
if (device.hasCapability("Color Control"))
|
|
||||||
{
|
|
||||||
log.debug "Device '${device.id}' has also the color capability. Subscribe to it."
|
|
||||||
subscribe(device, "color", colorStateChanged)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def switchStateChanged(eventArgs) { sendPushNotification("switch", eventArgs) }
|
|
||||||
def colorStateChanged(eventArgs) { sendPushNotification("color", eventArgs) }
|
|
||||||
|
|
||||||
def updateDevice() {
|
|
||||||
def device = getDevice(params.id)
|
|
||||||
def command = notNull("command", params.command)
|
|
||||||
|
|
||||||
log.debug "Executing '${command}' on device '${device.id}'."
|
|
||||||
|
|
||||||
switch(command) {
|
|
||||||
case "on":
|
|
||||||
case "On":
|
|
||||||
device.on()
|
|
||||||
break
|
|
||||||
|
|
||||||
case "off":
|
|
||||||
case "Off":
|
|
||||||
device.off()
|
|
||||||
break
|
|
||||||
|
|
||||||
case "toggle":
|
|
||||||
case "Toggle":
|
|
||||||
if (device.currentSwitch == "on")
|
|
||||||
device.off();
|
|
||||||
else
|
|
||||||
device.on();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
httpError(501, "'${command}' is not a valid command for '${device.id}'")
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDeviceInfos(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
def executeRoutine() {
|
|
||||||
def routine = getRoutine(params.id);
|
|
||||||
log.debug "Executing routine '${routine.id}' (${routine.label})"
|
|
||||||
|
|
||||||
location.helloHome?.execute(routine.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Region: Get device
|
|
||||||
def getDevices()
|
|
||||||
{
|
|
||||||
return switches
|
|
||||||
+ bubls
|
|
||||||
+ lights
|
|
||||||
+ outlets
|
|
||||||
+ relaySwitches;
|
|
||||||
}
|
|
||||||
|
|
||||||
def findDevice(deviceId)
|
|
||||||
{
|
|
||||||
notNull("deviceId", deviceId);
|
|
||||||
|
|
||||||
return getDevices().find { it.id == deviceId };
|
|
||||||
}
|
|
||||||
|
|
||||||
def getDevice(deviceId)
|
|
||||||
{
|
|
||||||
def device = findDevice(deviceId);
|
|
||||||
if (device == null)
|
|
||||||
{
|
|
||||||
httpError(404, "Device '${deviceId}' not found.")
|
|
||||||
}
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Region: Get routine
|
|
||||||
def findRoutine(routineId)
|
|
||||||
{
|
|
||||||
return location.helloHome?.getPhrases().find{ it.id == routineId};
|
|
||||||
}
|
|
||||||
|
|
||||||
def getRoutine(routineId)
|
|
||||||
{
|
|
||||||
def routine = findRoutine(routineId);
|
|
||||||
if (routine == null)
|
|
||||||
{
|
|
||||||
httpError(404, "Routine '${routineId}' not found.")
|
|
||||||
}
|
|
||||||
return routine;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Region: Parameters assertion helpers
|
|
||||||
def notNull(parameterName, value)
|
|
||||||
{
|
|
||||||
if(value == null || value == "")
|
|
||||||
{
|
|
||||||
httpError(404, "Missing parameter '${parameterName}'.")
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Region: Get infos
|
|
||||||
def getDeviceInfos(device, details = false)
|
|
||||||
{
|
|
||||||
def infos = [
|
|
||||||
id: device.id,
|
|
||||||
name: device.displayName,
|
|
||||||
state: device.currentValue("switch"),
|
|
||||||
color: device.currentValue("color"),
|
|
||||||
hue: device.currentValue("hue"),
|
|
||||||
saturation: device.currentValue("saturation"),
|
|
||||||
capabilities: device.capabilities.collect { getCapabilityInfos(it, details) }
|
|
||||||
]
|
|
||||||
|
|
||||||
if (details)
|
|
||||||
{
|
|
||||||
infos["attributes"] = device.supportedAttributes.collect { getAttributeInfos(it, details) }
|
|
||||||
infos["commands"] = device.supportedCommands.collect { getCommandInfos(it, details) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return infos;
|
|
||||||
}
|
|
||||||
|
|
||||||
def getCapabilityInfos(capablity, details = false)
|
|
||||||
{
|
|
||||||
def infos = [name: capablity.name]
|
|
||||||
|
|
||||||
if(details)
|
|
||||||
{
|
|
||||||
infos["attributes"] = capablity.attributes.collect { getAttributeInfos(it, details) }
|
|
||||||
infos["commands"] = capablity.commands.collect { getCommandInfos(it, details) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return infos;
|
|
||||||
}
|
|
||||||
|
|
||||||
def getCommandInfos(command, details = false)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
name: command.name,
|
|
||||||
arguments: command.arguments
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def getAttributeInfos(attribute, details = false)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
name: attribute.name,
|
|
||||||
arguments: attribute.dataType,
|
|
||||||
values: attribute.values
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def getRoutineInfos(routine, details = false)
|
|
||||||
{
|
|
||||||
def infos = [
|
|
||||||
id: routine.id,
|
|
||||||
name: routine.label
|
|
||||||
];
|
|
||||||
|
|
||||||
if (details)
|
|
||||||
{
|
|
||||||
infos["hasSecureActions"] = routine.hasSecureActions;
|
|
||||||
infos["action"] = routine.action;
|
|
||||||
}
|
|
||||||
|
|
||||||
return infos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Region: Push notification
|
|
||||||
def sendPushNotification(capability, eventArgs)
|
|
||||||
{
|
|
||||||
def deviceId = eventArgs.deviceId;
|
|
||||||
log.debug "Received notification for '${capability}' for device '${deviceId}'.";
|
|
||||||
|
|
||||||
def subscriptions = state.pushChannels.get(deviceId);
|
|
||||||
if (subscriptions == null || subscriptions.empty)
|
|
||||||
{
|
|
||||||
log.error "No subscription found for device ${deviceId}, unsubscribing!";
|
|
||||||
unsubscribe(eventArgs.device);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriptions.groupBy { it.channelUri }.each { sendPushNotification(capability, eventArgs, it.key, it.value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
def sendPushNotification(capability, eventArgs, channelUri, subscriptions)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
def request = [
|
|
||||||
uri: channelUri,
|
|
||||||
//headers: [name: "Authorization", value: "Bearer ${subscription.token}"],
|
|
||||||
body: [
|
|
||||||
location: [
|
|
||||||
id: eventArgs.locationId,
|
|
||||||
],
|
|
||||||
device: getDeviceInfos(eventArgs.device),
|
|
||||||
event: [
|
|
||||||
source: capability,
|
|
||||||
date: eventArgs.isoDate,
|
|
||||||
value: eventArgs.value,
|
|
||||||
name: eventArgs.name,
|
|
||||||
],
|
|
||||||
subscriptions: subscriptions.collect { [id: it.id, token: it.token] }
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
// Async post is still in beta stage ...
|
|
||||||
httpPostJson(request) { resp -> log.debug "response: ${resp.status}." }
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
log.error "Failed to push notification: ${e}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user