Compare commits

...

14 Commits

Author SHA1 Message Date
Richard Barnes
83af72efa6 MSA-1859: For pc control 2017-03-24 08:38:32 -07:00
bflorian
fbdaeea9ae Merge pull request #1811 from bflorian/DVCSMP-2399-zooz-zen20
DVCSMP-2399 Added Zooz ZEN20 DTH
2017-03-23 14:38:34 -07:00
Jack Chi
9fa9dee606 Merge pull request #1823 from parijatdas/logitech_harmony_hub
[CHF-574] Health check logitech-harmony-hub-c2c
2017-03-23 10:47:38 -07:00
Jack Chi
122a7d4146 Merge pull request #1827 from pchomal/plantlink_hc
[CHF-561] Health Check Plant Link Sensor
2017-03-23 09:44:32 -07:00
piyush.c
32eb95f7d7 [CHF-561] Health Check Plant Link Sensor 2017-03-23 16:09:27 +05:30
bflorian
343ec9d856 DVCSMP-2399 Added Zooz ZEN20 DTH 2017-03-22 20:00:54 -07:00
Parijat Das
cbd15ae9cc Added health-check for Logitech Harmony Hub 2017-03-22 16:57:11 -07:00
Jack Chi
78a509e8f5 Merge pull request #1555 from skt123/micro_smart_switch
[CHF-493] Added Health Check Implementation for Micro Smart Switch 2E.
2017-03-22 10:06:11 -07:00
Ryan Applegate
42d31f29cc Merge pull request #1820 from rappleg/FixSmartSenseMultiBackgroundColor
DVCSMP-2533 Fix backgroundColor on SmartSense Multi
2017-03-22 11:28:03 -05:00
rappleg
9b4f6974de DVCSMP-2533 Fix backgroundColor on SmartSense Multi 2017-03-22 11:21:04 -05:00
Sushant
ea5344f9c8 Merge branch 'master' into micro_smart_switch 2017-03-22 15:22:13 +05:30
sushant.k1
b5e1d652fd [CHF-493] Added Health Check Implementation for Micro Smart Switch 2E. 2017-03-22 15:17:55 +05:30
Jack Chi
a03e8f20c5 Merge pull request #1537 from pchomal/zwave_thermostat
[CHF-480] Health Check implementation for Z-Wave Thermostat
2017-03-21 18:01:13 -07:00
piyush.c
648dee90b6 [CHF-480]
Health Check implementation for Z-Wave Thermostat
2016-12-20 12:57:13 +05:30
11 changed files with 715 additions and 1 deletions

View File

@@ -7,6 +7,7 @@ metadata {
definition (name: "Logitech Harmony Hub C2C", namespace: "smartthings", author: "SmartThings") {
capability "Media Controller"
capability "Refresh"
capability "Health Check"
command "activityoff"
command "alloff"
@@ -38,6 +39,16 @@ metadata {
}
}
def installed() {
log.debug "installed()"
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "cloud", scheme:"untracked"]), displayed: false)
}
def updated() {
log.debug "updated()"
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "cloud", scheme:"untracked"]), displayed: false)
}
def startActivity(String activityId) {
log.debug "Executing 'Start Activity'"
log.trace parent.activity("$device.deviceNetworkId-$activityId","start")
@@ -58,6 +69,10 @@ def poll() {
log.trace parent.poll()
}
def ping() {
refresh()
}
def refresh() {
log.debug "Executing 'Refresh'"
log.trace parent.poll()

View File

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

View File

@@ -0,0 +1,35 @@
# Plant Link
Cloud Execution
Works with:
* [OSO Technologies PlantLink Soil Moisture Sensor](https://www.smartthings.com/works-with-smartthings/oso-technologies/oso-technologies-plantlink-soil-moisture-sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Troubleshooting](#troubleshooting)
## Capabilities
* **Relative Humidity Measurement** - allows reading the relative humidity from devices that support it
* **Sensor** - detects sensor events
* **Battery** - defines device uses a battery
* **Health Check** - indicates ability to get device health notifications
## Device Health
Plant Link sensor is a Z-wave sleepy device and checks in every 15 minutes.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
* __122min__ checkInterval
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
for the different models:
* [OSO Technologies PlantLink Soil Moisture Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206868986-PlantLink-Soil-Moisture-Sensor)

View File

@@ -21,8 +21,10 @@ metadata {
capability "Relative Humidity Measurement"
capability "Battery"
capability "Sensor"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000,0003,0405,FC08", outClusters: "0003"
fingerprint endpoint: "1", profileId: "0104", inClusters: "0000,0001,0003,0B04", outClusters: "0003", manufacturer: "", model: "", deviceJoinName: "OSO Technologies PlantLink Soil Moisture Sensor"
}
tiles {
@@ -48,6 +50,11 @@ metadata {
}
}
def updated() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "Parse description $description"

View File

@@ -52,7 +52,7 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"##e86d13"
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#e86d13"
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#00a0dc"
}
}

View File

@@ -0,0 +1,41 @@
/**
* Zooz Power Strip Outlet
*
* Copyright 2017 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.
*
*/
metadata {
definition (name: "Zooz Power Strip Outlet", namespace: "smartthings", author: "SmartThings") {
capability "Switch"
capability "Actuator"
capability "Sensor"
}
tiles {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState:"turningOn"
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC", nextState:"turningOff"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
}
}
}
void on() {
parent.childOn(device.deviceNetworkId)
}
void off() {
parent.childOff(device.deviceNetworkId)
}

View File

@@ -0,0 +1,210 @@
/**
* Zooz ZEN20 Power Strip Outlet
*
* Implementation of the Zooz ZEN20 power strip that uses the new composite device capabilities to provide individual
* control of each outlet from SmartApps as well as the mobile app. Incorporates contributions from:
*
* Eric Maycock (https://github.com/erocm123/SmartThingsPublic/blob/master/devicetypes/erocm123/zooz-power-strip.src/zooz-power-strip.groovy)
* Robert Vandervoort (https://github.com/robertvandervoort/SmartThings/blob/master/zooZ-Strip-ZEN20/device_type-zooZ-strip-ZEN20_v1.0)
*
* Copyright 2017 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.
*
*/
metadata {
definition (name: "Zooz Power Strip", namespace: "smartthings", author: "SmartThings") {
capability "Switch"
capability "Refresh"
capability "Actuator"
capability "Sensor"
fingerprint manufacturer: "015D", prod: "0651", model: "F51C", deviceJoinName: "Zooz ZEN 20 Power Strip"
}
tiles {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState:"turningOn"
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC", nextState:"turningOff"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
}
childDeviceTiles("outlets")
standardTile("refresh", "device.switch", width: 1, height: 1, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
}
}
/////////////////////////////
// Installation and update //
/////////////////////////////
def installed() {
createChildDevices()
}
def updated() {
if (!childDevices) {
createChildDevices()
}
else if (device.label != state.oldLabel) {
childDevices.each {
def newLabel = "${device.displayName} (CH${channelNumber(it.deviceNetworkId)})"
it.setLabel(newLabel)
}
state.oldLabel = device.label
}
}
//////////////////////
// Event Generation //
//////////////////////
def parse(String description) {
trace "parse('$description')"
def result = []
if (description.startsWith("Err")) {
result = createEvent(descriptionText:description, isStateChange:true)
} else if (description != "updated") {
def cmd = zwave.parse(description, [0x60: 3, 0x32: 3, 0x25: 1, 0x20: 1])
if (cmd) {
result += zwaveEvent(cmd, 1)
}
else {
log.warn "Unparsed description $description"
}
}
result
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd, ep) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x32: 3, 0x25: 1, 0x20: 1])
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint as Integer)
}
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, endpoint) {
trace "zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport $cmd, $endpoint)"
zwaveBinaryEvent(cmd, endpoint)
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd, endpoint) {
trace "zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport $cmd, $endpoint)"
zwaveBinaryEvent(cmd, endpoint)
}
def zwaveBinaryEvent(cmd, endpoint) {
def result = []
def children = childDevices
def childDevice = children.find{it.deviceNetworkId.endsWith("$endpoint")}
childDevice.sendEvent(name: "switch", value: cmd.value ? "on" : "off")
if (cmd.value) {
// One on and the strip is on
result << createEvent(name: "switch", value: "on")
} else {
// All off and the strip is off
if (! children.any { it.currentValue("switch") == "on" }) {
result << createEvent(name: "switch", value: "off")
}
}
result
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd, ep) {
updateDataValue("MSR", String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId))
return null
}
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, ep) {
trace "applicationVersion $cmd.applicationVersion"
}
def zwaveEvent(physicalgraph.zwave.Command cmd, ep) {
log.warn("${device.displayName}: Unhandled ${cmd}" + (ep ? " from endpoint $ep" : ""))
}
/////////////////////////////
// Installation and update //
/////////////////////////////
def on() {
def cmds = []
def cmd = zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF)
cmds << zwave.multiChannelV3.multiChannelCmdEncap(bitAddress: true, destinationEndPoint:0x1F).encapsulate(cmd).format()
cmds << "delay 400"
cmds.addAll(refresh())
return cmds
}
def off() {
def cmds = []
def cmd = zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00)
cmds << zwave.multiChannelV3.multiChannelCmdEncap(bitAddress: true, destinationEndPoint:0x1F).encapsulate(cmd).format()
cmds << "delay 400"
cmds.addAll(refresh())
return cmds
}
//////////////////////
// Child Device API //
//////////////////////
void childOn(String dni) {
onOffCmd(0xFF, channelNumber(dni))
}
void childOff(String dni) {
onOffCmd(0, channelNumber(dni))
}
def refresh() {
def cmds = (1..5).collect { endpoint ->
encap(zwave.switchBinaryV1.switchBinaryGet(), endpoint)
}
delayBetween(cmds, 100)
}
///////////////////
// Local Methods //
///////////////////
private channelNumber(String dni) {
dni.split("-ep")[-1] as Integer
}
private void onOffCmd(value, endpoint = null) {
def actions = [
new physicalgraph.device.HubAction(encap(zwave.basicV1.basicSet(value: value), endpoint)),
new physicalgraph.device.HubAction(encap(zwave.switchBinaryV1.switchBinaryGet(), endpoint)),
]
sendHubCommand(actions, 500)
}
private void createChildDevices() {
state.oldLabel = device.label
for (i in 1..5) {
addChildDevice("Zooz Power Strip Outlet", "${device.deviceNetworkId}-ep${i}", null,
[completedSetup: true, label: "${device.displayName} (CH${i})",
isComponent: true, componentName: "ch$i", componentLabel: "Channel $i"])
}
}
private encap(cmd, endpoint) {
if (endpoint) {
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:endpoint).encapsulate(cmd).format()
} else {
cmd.format()
}
}
private trace(msg) {
//log.trace(msg)
}

View File

@@ -22,10 +22,12 @@ metadata {
capability "Configuration"
capability "Sensor"
capability "Light"
capability "Health Check"
command "reset"
fingerprint inClusters: "0x25,0x32"
fingerprint mfr:"0086", prod:"0003", model:"0012", deviceJoinName: "Aeon Labs Micro Smart Switch 2E"
}
// simulator metadata
@@ -73,6 +75,8 @@ 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])
try {
if (!state.MSR) {
response(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
@@ -179,6 +183,14 @@ def poll() {
])
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
log.debug "ping() called"
refresh()
}
def refresh() {
delayBetween([
zwave.switchBinaryV1.switchBinaryGet().format(),

View File

@@ -20,6 +20,7 @@ metadata {
capability "Configuration"
capability "Polling"
capability "Sensor"
capability "Health Check"
attribute "thermostatFanState", "string"
@@ -30,6 +31,7 @@ metadata {
fingerprint deviceId: "0x08"
fingerprint inClusters: "0x43,0x40,0x44,0x31"
fingerprint mfr:"0039", prod:"0011", model:"0001", deviceJoinName: "Honeywell Z-Wave Thermostat"
}
// simulator metadata
@@ -123,6 +125,11 @@ 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 parse(String description)
{
def map = createEvent(zwaveEvent(zwave.parse(description, [0x42:1, 0x43:2, 0x31: 3])))
@@ -393,6 +400,14 @@ 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"
poll()
}
def configure() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSupportedGet().format(),

View File

@@ -511,6 +511,10 @@ def pollResponse(response, data) {
if (ResponseValues) {
def map = [:]
ResponseValues.hubs.each {
// Device-Watch relies on the Logitech Harmony Cloud to get the Device state.
def isAlive = it.value.status
def d = getChildDevice("harmony-${it.key}")
d?.sendEvent(name: "DeviceWatch-DeviceStatus", value: isAlive!=504? "online":"offline", displayed: false, isStateChange: true)
if (it.value.message == "OK") {
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
def hub = getChildDevice("harmony-${it.key}")

View File

@@ -0,0 +1,373 @@
/**
* 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}"
}
}