Compare commits

..

6 Commits

Author SHA1 Message Date
Aaron Miller
d7b448b699 Merge pull request #2016 from aaron-miller/DVCSMP-2665
DVCSMP-2665 Prevent 'Smart Nightlight' from over scheduling
2017-05-22 09:24:22 -05:00
Aaron Miller
9285536f73 Merge pull request #2013 from aaron-miller/DVCSMP-2659
DVCSMP-2659 Reduce Hello Home Phrase Detector Logs
2017-05-22 09:23:22 -05:00
Aaron Miller
e7713caec9 DVCSMP-2665 Prevent 'Smart Nightlight' from over scheduling 2017-05-19 14:09:08 -05:00
Aaron Miller
f0de2f1a19 DVCSMP-2659 Reduce Hello Home Phrase Detector Logs 2017-05-18 09:56:17 -05:00
Jack Chi
01c2968f91 Merge pull request #1834 from parijatdas/zwave_water_valve
[CHF-569] Health Check zwave-water-valve
2017-05-17 17:05:10 -07:00
Parijat Das
12bb6c0492 Added health-check for Z-wave Water Valve 2017-05-16 12:26:48 +05:30
6 changed files with 69 additions and 386 deletions

View File

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

View 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)

View File

@@ -14,12 +14,14 @@
metadata {
definition (name: "Z-Wave Water Valve", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Health Check"
capability "Valve"
capability "Polling"
capability "Refresh"
capability "Sensor"
fingerprint deviceId: "0x1006", inClusters: "0x25"
fingerprint mfr:"0173", prod:"0003", model:"0002", deviceJoinName: "Leak Intelligence Leak Gopher Water Shutoff Valve"
}
// simulator metadata
@@ -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() {
// 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())
}
@@ -114,6 +123,13 @@ def poll() {
zwave.switchBinaryV1.switchBinaryGet().format()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
refresh()
}
def refresh() {
log.debug "refresh() is called"
def commands = [zwave.switchBinaryV1.switchBinaryGet().format()]

View File

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

View File

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

View File

@@ -125,19 +125,19 @@
if(allOk) {
if(everyoneIsAway() && (state.sunMode == "sunrise")) {
log.info("Home is Empty Setting New Away Mode")
log.debug("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.info("Home is Empty Setting New Away Mode")
log.debug("Home is Empty Setting New Away Mode")
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
runIn(delay, "setAway")
}
else {
log.info("Home is Occupied Setting New Home Mode")
log.debug("Home is Occupied Setting New Home Mode")
setHome()
@@ -152,7 +152,7 @@
log.debug("Checking if everyone is away")
if(everyoneIsAway()) {
log.info("Nobody is home, running away sequence")
log.debug("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.info("Someone is home, running home sequence")
log.debug("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.info(message)
log.debug(message)
sendAway(message)
location.helloHome.execute(settings.awayNight)
}
else if(state.sunMode == "sunrise") {
def message = "Performing \"${awayDay}\" for you as requested."
log.info(message)
log.debug(message)
sendAway(message)
location.helloHome.execute(settings.awayDay)
}
@@ -192,19 +192,19 @@
}
else {
log.info("Somebody returned home before we set to '${newAwayMode}'")
log.debug("Somebody returned home before we set to '${newAwayMode}'")
}
}
//set home mode when house is occupied
def setHome() {
sendOutOfDateNotification()
log.info("Setting Home Mode!!")
log.debug("Setting Home Mode!!")
if(anyoneIsHome()) {
if(state.sunMode == "sunset"){
if (location.mode != "${homeModeNight}"){
def message = "Performing \"${homeNight}\" for you as requested."
log.info(message)
log.debug(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.info(message)
log.debug(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()
}
}
}