mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-29 06:13:07 +01:00
Compare commits
1 Commits
MSA-1859-1
...
MSA-1854-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbf8d80504 |
@@ -7,7 +7,6 @@ metadata {
|
|||||||
definition (name: "Logitech Harmony Hub C2C", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Logitech Harmony Hub C2C", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Media Controller"
|
capability "Media Controller"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "activityoff"
|
command "activityoff"
|
||||||
command "alloff"
|
command "alloff"
|
||||||
@@ -39,16 +38,6 @@ 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) {
|
def startActivity(String activityId) {
|
||||||
log.debug "Executing 'Start Activity'"
|
log.debug "Executing 'Start Activity'"
|
||||||
log.trace parent.activity("$device.deviceNetworkId-$activityId","start")
|
log.trace parent.activity("$device.deviceNetworkId-$activityId","start")
|
||||||
@@ -69,10 +58,6 @@ def poll() {
|
|||||||
log.trace parent.poll()
|
log.trace parent.poll()
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "Executing 'Refresh'"
|
log.debug "Executing 'Refresh'"
|
||||||
log.trace parent.poll()
|
log.trace parent.poll()
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@@ -21,10 +21,8 @@ metadata {
|
|||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0405,FC08", outClusters: "0003"
|
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 {
|
tiles {
|
||||||
@@ -50,11 +48,6 @@ 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
|
// Parse incoming device messages to generate events
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "Parse description $description"
|
log.debug "Parse description $description"
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
|
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
|
||||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
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"
|
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#00a0dc"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
@@ -22,12 +22,10 @@ metadata {
|
|||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Light"
|
capability "Light"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "reset"
|
command "reset"
|
||||||
|
|
||||||
fingerprint inClusters: "0x25,0x32"
|
fingerprint inClusters: "0x25,0x32"
|
||||||
fingerprint mfr:"0086", prod:"0003", model:"0012", deviceJoinName: "Aeon Labs Micro Smart Switch 2E"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -75,8 +73,6 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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])
|
|
||||||
try {
|
try {
|
||||||
if (!state.MSR) {
|
if (!state.MSR) {
|
||||||
response(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
|
response(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
|
||||||
@@ -183,14 +179,6 @@ def poll() {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
log.debug "ping() called"
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
delayBetween([
|
delayBetween([
|
||||||
zwave.switchBinaryV1.switchBinaryGet().format(),
|
zwave.switchBinaryV1.switchBinaryGet().format(),
|
||||||
|
|||||||
211
smartapps/global-ipl-ijini/ijini.src/ijini.groovy
Normal file
211
smartapps/global-ipl-ijini/ijini.src/ijini.groovy
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
/**
|
||||||
|
* IJINI
|
||||||
|
*
|
||||||
|
* Copyright 2017 IPL_DevAccount
|
||||||
|
*
|
||||||
|
* 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: "IJINI",
|
||||||
|
namespace: "global.ipl.IJINI",
|
||||||
|
author: "IPL_DevAccount",
|
||||||
|
description: "IJINI SUPPORTING APP",
|
||||||
|
category: "SmartThings Labs",
|
||||||
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||||
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||||
|
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
|
||||||
|
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
section("Allow Endpoint to Control These Things...") {
|
||||||
|
input "switches", "capability.switch", title: "Which Switches?", multiple: true
|
||||||
|
//input "locks", "capability.lock", title: "Which Locks?", multiple: true
|
||||||
|
input "themotion", "capability.motionSensor", title: "Which Motions?", multiple: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mappings {
|
||||||
|
|
||||||
|
path("/switches") {
|
||||||
|
action: [
|
||||||
|
GET: "listSwitches"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/switches/:id") {
|
||||||
|
action: [
|
||||||
|
GET: "showSwitch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/switches/:id/:command") {
|
||||||
|
action: [
|
||||||
|
GET: "updateSwitch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
path("/locks") {
|
||||||
|
action: [
|
||||||
|
GET: "listLocks"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/locks/:id") {
|
||||||
|
action: [
|
||||||
|
GET: "showLock"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/locks/:id/:command") {
|
||||||
|
action: [
|
||||||
|
GET: "updateLock"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
path("/SwitchState/:id") {
|
||||||
|
action: [
|
||||||
|
GET: "SwitchState"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def SwitchState()
|
||||||
|
{
|
||||||
|
GetSwitchState(switches,"switch");
|
||||||
|
}
|
||||||
|
|
||||||
|
def GetSwitchState(devices, type)
|
||||||
|
{
|
||||||
|
|
||||||
|
def device = devices.find { it.id == params.id }
|
||||||
|
if (!device) {
|
||||||
|
httpError(404, "Device not found ${devices}")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
def attributeName = (type == "motionSensor") ? "motion" : type
|
||||||
|
def s = device.currentState(attributeName)
|
||||||
|
[value: s?.value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated()
|
||||||
|
{
|
||||||
|
unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
{
|
||||||
|
subscribe(themotion, "motion.active", motionDetectedHandler)
|
||||||
|
subscribe(themotion, "motion.inactive", motionStoppedHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
def motionDetectedHandler(evt) {
|
||||||
|
log.debug "motionDetectedHandler called: ${evt}"
|
||||||
|
httpGet(String uri, Closure closure)
|
||||||
|
}
|
||||||
|
|
||||||
|
def motionStoppedHandler(evt) {
|
||||||
|
log.debug "motionStoppedHandler called: ${evt}"
|
||||||
|
}
|
||||||
|
|
||||||
|
//switches
|
||||||
|
def listSwitches() {
|
||||||
|
switches.collect{device(it,"switch")}
|
||||||
|
}
|
||||||
|
def showSwitch() {
|
||||||
|
show(switches, "switch")
|
||||||
|
}
|
||||||
|
void updateSwitch() {
|
||||||
|
update(switches)
|
||||||
|
}
|
||||||
|
|
||||||
|
//locks
|
||||||
|
def listLocks() {
|
||||||
|
locks.collect{device(it,"lock")}
|
||||||
|
}
|
||||||
|
def showLock() {
|
||||||
|
show(locks, "lock")
|
||||||
|
}
|
||||||
|
void updateLock() {
|
||||||
|
update(locks)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Motions
|
||||||
|
def listMotion() {
|
||||||
|
log.debug "listMotions is activated...."
|
||||||
|
motion.collect{device(it,"motion")}
|
||||||
|
}
|
||||||
|
|
||||||
|
def showMotion() {
|
||||||
|
show(motion, "motionSensor")
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateMotion() {
|
||||||
|
update(motion)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def deviceHandler(evt) {
|
||||||
|
log.debug "deviceHandler"
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(devices)
|
||||||
|
{
|
||||||
|
log.debug "update, request: params: ${params}, devices: $devices.id"
|
||||||
|
|
||||||
|
//def command = request.JSON?.command
|
||||||
|
def command = params.command
|
||||||
|
//let's create a toggle option here
|
||||||
|
if (command)
|
||||||
|
{
|
||||||
|
def device = devices.find { it.id == params.id }//it, params 상에서 같은것을 찾아내서 저장...하는듯하다.
|
||||||
|
if (!device) {
|
||||||
|
httpError(404, "Device not found")
|
||||||
|
} else {
|
||||||
|
if(command == "toggle")
|
||||||
|
{
|
||||||
|
if(device.currentValue('switch') == "on")
|
||||||
|
{
|
||||||
|
device.off();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
device.on();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
device."$command"()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private show(devices, type) {
|
||||||
|
log.debug "device = ${devices},type = ${type} "
|
||||||
|
def device = devices.find { it.id == params.id }
|
||||||
|
if (!device) {
|
||||||
|
httpError(404, "Device not found")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
def attributeName = (type == "motionSensor") ? "motion" : type
|
||||||
|
def s = device.currentState(attributeName)
|
||||||
|
|
||||||
|
[id: device.id, label: device.displayName, value: s?.value, unitTime: s?.date?.time, type: type]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private device(it, type) {
|
||||||
|
log.debug "device ctrl.. ${it}, ${type}"
|
||||||
|
it ? [id: it.id, label: it.label, type: type] : null
|
||||||
|
}
|
||||||
@@ -511,10 +511,6 @@ def pollResponse(response, data) {
|
|||||||
if (ResponseValues) {
|
if (ResponseValues) {
|
||||||
def map = [:]
|
def map = [:]
|
||||||
ResponseValues.hubs.each {
|
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") {
|
if (it.value.message == "OK") {
|
||||||
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
|
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
|
||||||
def hub = getChildDevice("harmony-${it.key}")
|
def hub = getChildDevice("harmony-${it.key}")
|
||||||
|
|||||||
@@ -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