mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 13:21:53 +00:00
Compare commits
13 Commits
PROD_2016.
...
MSA-1451-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73e73dc5a6 | ||
|
|
156adc3b86 | ||
|
|
17014dd248 | ||
|
|
555edf623a | ||
|
|
feb6ba0e24 | ||
|
|
1ec110155d | ||
|
|
fadc496e1f | ||
|
|
0a2f2bffc2 | ||
|
|
8dc36eb8f6 | ||
|
|
d0929ab89e | ||
|
|
1ddd0632c9 | ||
|
|
53d0957383 | ||
|
|
b4a4d83ce7 |
@@ -22,7 +22,6 @@ metadata {
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
attribute "tamper", "enum", ["detected", "clear"]
|
||||
attribute "batteryStatus", "string"
|
||||
@@ -328,9 +327,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// allow device user configured or default 16 min to check in; double the periodic reporting interval
|
||||
sendEvent(name: "checkInterval", value: 2* (timeOptionValueMap[reportInterval] ?: (2*8*60)), displayed: false)
|
||||
|
||||
// This sensor joins as a secure device if you double-click the button to include it
|
||||
log.debug "${device.displayName} is configuring its settings"
|
||||
def request = []
|
||||
|
||||
@@ -20,7 +20,6 @@ metadata {
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
command "configureAfterSecure"
|
||||
|
||||
@@ -248,8 +247,6 @@ def configureAfterSecure() {
|
||||
def configure() {
|
||||
// log.debug "configure()"
|
||||
//["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
|
||||
// allow device 16 min to check in; double the periodic reporting interval
|
||||
sendEvent(name: "checkInterval", value: 2*8*60, displayed: false)
|
||||
}
|
||||
|
||||
private setConfigured() {
|
||||
|
||||
@@ -20,7 +20,6 @@ metadata {
|
||||
capability "Illuminance Measurement"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86"
|
||||
}
|
||||
@@ -181,9 +180,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// allow device 10 min to check in; double the periodic reporting interval
|
||||
sendEvent(name: "checkInterval", value: 2*5*60, displayed: false)
|
||||
|
||||
delayBetween([
|
||||
// send binary sensor report instead of basic set for motion
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(),
|
||||
|
||||
@@ -23,6 +23,7 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
|
||||
}
|
||||
@@ -66,6 +67,12 @@ def parse(String description) {
|
||||
def resultMap = zigbee.getEvent(description)
|
||||
if (resultMap) {
|
||||
sendEvent(resultMap)
|
||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
||||
if (state.lastActivity == null){
|
||||
state.lastActivity = now()
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
state.lastActivity = now()
|
||||
}
|
||||
else {
|
||||
log.debug "DID NOT PARSE MESSAGE for description : $description"
|
||||
@@ -85,6 +92,21 @@ def setLevel(value) {
|
||||
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
|
||||
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
||||
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
|
||||
state.lastActivity = null
|
||||
return zigbee.levelRefresh()
|
||||
} else {
|
||||
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||
}
|
||||
@@ -95,5 +117,6 @@ def poll() {
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -143,51 +144,14 @@ private Map parseReportAttributeMessage(String description) {
|
||||
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0030': // Closed/No Motion/Dry
|
||||
log.debug 'no motion'
|
||||
resultMap.name = 'motion'
|
||||
resultMap.value = 'inactive'
|
||||
break
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
Map resultMap = [:]
|
||||
|
||||
case '0x0032': // Open/Motion/Wet
|
||||
log.debug 'motion'
|
||||
resultMap.name = 'motion'
|
||||
resultMap.value = 'active'
|
||||
break
|
||||
result.name = 'motion'
|
||||
result.value = zs.isAlarm2Set() ? 'active' : 'inactive'
|
||||
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
|
||||
|
||||
case '0x0032': // Tamper Alarm
|
||||
log.debug 'motion with tamper alarm'
|
||||
resultMap.name = 'motion'
|
||||
resultMap.value = 'active'
|
||||
break
|
||||
|
||||
case '0x0033': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0034': // Supervision Report
|
||||
log.debug 'no motion with tamper alarm'
|
||||
resultMap.name = 'motion'
|
||||
resultMap.value = 'inactive'
|
||||
break
|
||||
|
||||
case '0x0035': // Restore Report
|
||||
break
|
||||
|
||||
case '0x0036': // Trouble/Failure
|
||||
log.debug 'motion with failure alarm'
|
||||
resultMap.name = 'motion'
|
||||
resultMap.value = 'active'
|
||||
break
|
||||
|
||||
case '0x0038': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def refresh()
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
|
||||
capability "Battery"
|
||||
@@ -219,40 +222,33 @@ private Map parseReportAttributeMessage(String description) {
|
||||
}
|
||||
|
||||
private List parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(" ")
|
||||
String msgCode = parsedMsg[2]
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
log.debug "parseIasMessage: $description"
|
||||
|
||||
List resultListMap = []
|
||||
Map resultMap_battery = [:]
|
||||
Map resultMap_battery_state = [:]
|
||||
Map resultMap_sensor = [:]
|
||||
|
||||
// Relevant bit field definitions from ZigBee spec
|
||||
def BATTERY_BIT = ( 1 << 3 )
|
||||
def TROUBLE_BIT = ( 1 << 6 )
|
||||
def SENSOR_BIT = ( 1 << 0 ) // it's ALARM1 bit from the ZCL spec
|
||||
|
||||
// Convert hex string to integer
|
||||
def zoneStatus = Integer.parseInt(msgCode[-4..-1],16)
|
||||
|
||||
log.debug "parseIasMessage: zoneStatus: ${zoneStatus}"
|
||||
resultMap_sensor.name = "contact"
|
||||
resultMap_sensor.value = zs.isAlarm1Set() ? "open" : "closed"
|
||||
|
||||
// Check each relevant bit, create map for it, and add to list
|
||||
log.debug "parseIasMessage: Battery Status ${zoneStatus & BATTERY_BIT}"
|
||||
log.debug "parseIasMessage: Trouble Status ${zoneStatus & TROUBLE_BIT}"
|
||||
log.debug "parseIasMessage: Sensor Status ${zoneStatus & SENSOR_BIT}"
|
||||
log.debug "parseIasMessage: Battery Status ${zs.battery}"
|
||||
log.debug "parseIasMessage: Trouble Status ${zs.trouble}"
|
||||
log.debug "parseIasMessage: Sensor Status ${zs.alarm1}"
|
||||
|
||||
/* Comment out this path to check the battery state to avoid overwriting the
|
||||
battery value (Change log #2), but keep these conditions for later use
|
||||
resultMap_battery_state.name = "battery_state"
|
||||
if (zoneStatus & TROUBLE_BIT) {
|
||||
if (zs.isTroubleSet()) {
|
||||
resultMap_battery_state.value = "failed"
|
||||
|
||||
resultMap_battery.name = "battery"
|
||||
resultMap_battery.value = 0
|
||||
}
|
||||
else {
|
||||
if (zoneStatus & BATTERY_BIT) {
|
||||
if (zs.isBatterySet()) {
|
||||
resultMap_battery_state.value = "low"
|
||||
|
||||
// to generate low battery notification by the platform
|
||||
@@ -270,9 +266,6 @@ private List parseIasMessage(String description) {
|
||||
}
|
||||
*/
|
||||
|
||||
resultMap_sensor.name = "contact"
|
||||
resultMap_sensor.value = (zoneStatus & SENSOR_BIT) ? "open" : "closed"
|
||||
|
||||
resultListMap << resultMap_battery_state
|
||||
resultListMap << resultMap_battery
|
||||
resultListMap << resultMap_sensor
|
||||
|
||||
@@ -101,6 +101,12 @@ def parse(String description) {
|
||||
else {
|
||||
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
|
||||
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
|
||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
||||
if (state.lastActivity == null){
|
||||
state.lastActivity = now()
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
state.lastActivity = now()
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -116,13 +122,27 @@ def off() {
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
|
||||
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
||||
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
|
||||
state.lastActivity = null
|
||||
return zigbee.onOffRefresh()
|
||||
} else {
|
||||
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
|
||||
zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 1200, displayed: false)
|
||||
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
|
||||
zigbee.onOffConfig() + powerConfig() + refresh()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
@@ -99,6 +101,13 @@ def parse(String description) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
||||
if (state.lastActivity == null){
|
||||
state.lastActivity = now()
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
state.lastActivity = now()
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
@@ -170,42 +179,9 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getMoistureResult('dry')
|
||||
break
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getMoistureResult('wet')
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
log.debug 'dry with tamper alarm'
|
||||
resultMap = getMoistureResult('dry')
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
log.debug 'water with tamper alarm'
|
||||
resultMap = getMoistureResult('wet')
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
@@ -302,6 +278,21 @@ private Map getMoistureResult(value) {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
|
||||
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
||||
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
|
||||
state.lastActivity = null
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
} else {
|
||||
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Temperature and Battery"
|
||||
def refreshCmds = [
|
||||
@@ -313,7 +304,7 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"])
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
@@ -103,6 +105,13 @@ def parse(String description) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
||||
if (state.lastActivity == null){
|
||||
state.lastActivity = now()
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
state.lastActivity = now()
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
@@ -183,44 +192,10 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getMotionResult('inactive')
|
||||
break
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
log.debug 'motion with tamper alarm'
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
log.debug 'no motion with tamper alarm'
|
||||
resultMap = getMotionResult('inactive')
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
log.debug 'motion with failure alarm'
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
|
||||
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
@@ -314,6 +289,21 @@ private Map getMotionResult(value) {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
|
||||
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
||||
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
|
||||
state.lastActivity = null
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
} else {
|
||||
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh called"
|
||||
def refreshCmds = [
|
||||
@@ -325,7 +315,7 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"])
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -168,44 +169,8 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getMotionResult('inactive')
|
||||
break
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
log.debug 'motion with tamper alarm'
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
log.debug 'no motion with tamper alarm'
|
||||
resultMap = getMotionResult('inactive')
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
log.debug 'motion with failure alarm'
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
return zs.isAlarm1Set() ? getMotionResult('active') : getMotionResult('inactive')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
@@ -126,6 +127,13 @@ def parse(String description) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
||||
if (state.lastActivity == null){
|
||||
state.lastActivity = now()
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
state.lastActivity = now()
|
||||
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
@@ -224,47 +232,13 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
|
||||
if (garageSensor != "Yes"){
|
||||
resultMap = getContactResult('closed')
|
||||
resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
||||
}
|
||||
break
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
if (garageSensor != "Yes"){
|
||||
resultMap = getContactResult('open')
|
||||
}
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
if (garageSensor != "Yes"){
|
||||
resultMap = getContactResult('closed')
|
||||
}
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
if (garageSensor != "Yes"){
|
||||
resultMap = getContactResult('open')
|
||||
}
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
@@ -397,6 +371,21 @@ private getAccelerationResult(numValue) {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
|
||||
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
||||
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
|
||||
state.lastActivity = null
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
} else {
|
||||
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Values "
|
||||
|
||||
@@ -424,7 +413,7 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"])
|
||||
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
*
|
||||
*/
|
||||
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
@@ -172,40 +173,9 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getContactResult('closed')
|
||||
break
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getContactResult('open')
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
resultMap = getContactResult('closed')
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
resultMap = getContactResult('open')
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
@@ -91,6 +92,13 @@ def parse(String description) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
||||
if (state.lastActivity == null){
|
||||
state.lastActivity = now()
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
state.lastActivity = now()
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
@@ -167,40 +175,8 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getContactResult('closed')
|
||||
break
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getContactResult('open')
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
resultMap = getContactResult('closed')
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
resultMap = getContactResult('open')
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
@@ -265,6 +241,21 @@ private Map getContactResult(value) {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
|
||||
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
||||
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
|
||||
state.lastActivity = null
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
} else {
|
||||
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Temperature and Battery"
|
||||
def refreshCmds = [
|
||||
@@ -276,7 +267,7 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"])
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
@@ -83,6 +83,13 @@ def parse(String description) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
|
||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
||||
if (state.lastActivity == null){
|
||||
state.lastActivity = now()
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
state.lastActivity = now()
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
return map ? createEvent(map) : null
|
||||
}
|
||||
@@ -239,6 +246,20 @@ private Map getHumidityResult(value) {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
||||
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
|
||||
state.lastActivity = null
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
} else {
|
||||
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh()
|
||||
{
|
||||
log.debug "refresh temperature, humidity, and battery"
|
||||
@@ -254,7 +275,7 @@ def refresh()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"])
|
||||
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
def configCmds = [
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -161,40 +162,9 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getContactResult('closed')
|
||||
break
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getContactResult('open')
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
resultMap = getContactResult('closed')
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
resultMap = getContactResult('open')
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
|
||||
@@ -19,6 +19,7 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Health Check"
|
||||
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
|
||||
@@ -53,6 +54,12 @@ def parse(String description) {
|
||||
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
||||
if (state.lastActivity == null){
|
||||
state.lastActivity = now()
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
state.lastActivity = now()
|
||||
if (event.name=="level" && event.value==0) {}
|
||||
else {
|
||||
sendEvent(event)
|
||||
@@ -75,6 +82,20 @@ def on() {
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
|
||||
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
||||
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
|
||||
state.lastActivity = null
|
||||
return zigbee.onOffRefresh()
|
||||
} else {
|
||||
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||
@@ -82,5 +103,7 @@ def refresh() {
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
|
||||
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Health Check"
|
||||
|
||||
attribute "colorName", "string"
|
||||
command "setGenericName"
|
||||
@@ -84,6 +85,12 @@ def parse(String description) {
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
log.debug event
|
||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
||||
if (state.lastActivity == null){
|
||||
state.lastActivity = now()
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
state.lastActivity = now()
|
||||
if (event.name=="level" && event.value==0) {}
|
||||
else {
|
||||
if (event.name=="colorTemperature") {
|
||||
@@ -119,13 +126,29 @@ def on() {
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
|
||||
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
||||
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
|
||||
state.lastActivity = null
|
||||
return zigbee.onOffRefresh()
|
||||
} else {
|
||||
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
|
||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
|
||||
zigbee.onOffRefresh() + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
|
||||
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
|
||||
927
smartapps/citrix/octoblu.src/octoblu.groovy
Normal file
927
smartapps/citrix/octoblu.src/octoblu.groovy
Normal file
@@ -0,0 +1,927 @@
|
||||
/**
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2016 Octoblu
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
import org.apache.commons.codec.binary.Base64
|
||||
import java.text.DecimalFormat
|
||||
import groovy.transform.Field
|
||||
|
||||
@Field final USE_DEBUG = true
|
||||
@Field final selectedCapabilities = [ "actuator", "sensor" ]
|
||||
|
||||
private getVendorName() { "Octoblu" }
|
||||
private getVendorIcon() { "http://i.imgur.com/BjTfDYk.png" }
|
||||
private apiUrl() { appSettings.apiUrl ?: "https://meshblu.octoblu.com/" }
|
||||
private getVendorAuthPath() { appSettings.vendorAuthPath ?: "https://oauth.octoblu.com/authorize" }
|
||||
private getVendorTokenPath() { appSettings.vendorTokenPath ?: "https://oauth.octoblu.com/access_token" }
|
||||
|
||||
definition(
|
||||
name: "Octoblu",
|
||||
namespace: "citrix",
|
||||
author: "Octoblu",
|
||||
description: "Connect SmartThings devices to Octoblu",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "http://i.imgur.com/BjTfDYk.png",
|
||||
iconX2Url: "http://i.imgur.com/BjTfDYk.png"
|
||||
) {
|
||||
appSetting "apiUrl"
|
||||
appSetting "vendorAuthPath"
|
||||
appSetting "vendorTokenPath"
|
||||
}
|
||||
|
||||
preferences {
|
||||
page(name: "welcomePage")
|
||||
page(name: "authPage")
|
||||
page(name: "subscribePage")
|
||||
page(name: "devicesPage")
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/oauthCode") {
|
||||
action: [ GET: "getOauthCode" ]
|
||||
}
|
||||
path("/message") {
|
||||
action: [ POST: "postMessage" ]
|
||||
}
|
||||
path("/app") {
|
||||
action: [ POST: "postApp" ]
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
def getDevInfo() {
|
||||
return state.vendorDevices.collect { k, v -> "${v.uuid} " }.sort().join(" \n")
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
def welcomePage() {
|
||||
cleanUpTokens()
|
||||
|
||||
return dynamicPage(name: "welcomePage", nextPage: "authPage", uninstall: showUninstall) {
|
||||
section {
|
||||
paragraph title: "Welcome to the Octoblu SmartThings App!", "press 'Next' to continue"
|
||||
}
|
||||
if (state.vendorDevices && state.vendorDevices.size()>0) {
|
||||
section {
|
||||
paragraph title: "My SmartThings in Octobu (${state.vendorDevices.size()}):", getDevInfo()
|
||||
}
|
||||
}
|
||||
if (state.installed) {
|
||||
section {
|
||||
input name: "showUninstall", type: "bool", title: "Uninstall", submitOnChange: true
|
||||
if (showUninstall) {
|
||||
state.removeDevices = removeDevices
|
||||
input name: "removeDevices", type: "bool", title: "Remove Octoblu devices", submitOnChange: true
|
||||
paragraph title: "Sorry to see you go!", "please email <support@octoblu.com> with any feedback or issues"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
def authPage() {
|
||||
|
||||
if (!state.accessToken) {
|
||||
createAccessToken()
|
||||
}
|
||||
|
||||
debug "using app access token ${state.accessToken}"
|
||||
|
||||
if (!state.vendorOAuthToken) {
|
||||
createOAuthDevice()
|
||||
}
|
||||
|
||||
def oauthParams = [
|
||||
response_type: "code",
|
||||
client_id: state.vendorOAuthUuid,
|
||||
redirect_uri: getApiServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/oauthCode"
|
||||
]
|
||||
|
||||
def redirectUrl = getVendorAuthPath() + '?' + toQueryString(oauthParams)
|
||||
debug "tokened redirect_uri = ${oauthParams.redirect_uri}"
|
||||
|
||||
def isRequired = !state.vendorBearerToken
|
||||
return dynamicPage(name: "authPage", title: "Octoblu Authentication", nextPage:(isRequired ? null : "subscribePage"), install: isRequired) {
|
||||
section {
|
||||
debug "url: ${redirectUrl}"
|
||||
if (isRequired) {
|
||||
href url:redirectUrl, style:"embedded", title: "Authorize with Octoblu", required: isRequired, description:"please login with Octoblu to complete setup"
|
||||
} else {
|
||||
paragraph title: "Please press 'Next' to continue", "Octoblu token has been created"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def createOAuthDevice() {
|
||||
def oAuthDevice = [
|
||||
"name": "SmartThings",
|
||||
"owner": "68c39f40-cc13-4560-a68c-e8acd021cff9",
|
||||
"type": "device:oauth",
|
||||
"online": true,
|
||||
"options": [
|
||||
"name": "SmartThings",
|
||||
"imageUrl": "https://i.imgur.com/TsXefbK.png",
|
||||
"callbackUrl": getApiServerUrl() + "/api"
|
||||
],
|
||||
"configureWhitelist": [ "68c39f40-cc13-4560-a68c-e8acd021cff9" ],
|
||||
"discoverWhitelist": [ "*" ],
|
||||
"receiveWhitelist": [],
|
||||
"sendWhitelist": []
|
||||
]
|
||||
|
||||
def postParams = [ uri: apiUrl()+"devices",
|
||||
body: groovy.json.JsonOutput.toJson(oAuthDevice)]
|
||||
|
||||
try {
|
||||
httpPostJson(postParams) { response ->
|
||||
debug "got new token for oAuth device ${response.data}"
|
||||
state.vendorOAuthUuid = response.data.uuid
|
||||
state.vendorOAuthToken = response.data.token
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "unable to create oAuth device: ${e}"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
def subscribePage() {
|
||||
return dynamicPage(name: "subscribePage", title: "Subscribe to SmartThing devices", nextPage: "devicesPage") {
|
||||
section {
|
||||
// input name: "selectedCapabilities", type: "enum", title: "capability filter",
|
||||
// submitOnChange: true, multiple: true, required: false, options: [ "actuator", "sensor" ]
|
||||
for (capability in selectedCapabilities) {
|
||||
input name: "${capability}Capability".toString(), type: "capability.$capability", title: "${capability.capitalize()} Things", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
section(" ") {
|
||||
input name: "pleaseCreateAppDevice", type: "bool", title: "Create a SmartApp device", defaultValue: true
|
||||
paragraph "A SmartApp device allows access to location and hub information for this installation"
|
||||
}
|
||||
section(" ") {
|
||||
paragraph title: "", "Existing Octoblu devices may be modified!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
def devicesPage() {
|
||||
def postParams = [
|
||||
uri: apiUrl() + "devices?owner=${state.vendorUuid}&category=smart-things",
|
||||
headers: ["Authorization": "Bearer ${state.vendorBearerToken}"]
|
||||
]
|
||||
state.vendorDevices = [:]
|
||||
|
||||
def hasDevice = [:]
|
||||
hasDevice[app.id] = true
|
||||
selectedCapabilities.each { capability ->
|
||||
def smartDevices = settings["${capability}Capability"]
|
||||
smartDevices.each { smartDevice ->
|
||||
hasDevice[smartDevice.id] = true
|
||||
}
|
||||
}
|
||||
|
||||
debug "getting url ${postParams.uri}"
|
||||
try {
|
||||
httpGet(postParams) { response ->
|
||||
debug "devices json ${response.data.devices}"
|
||||
response.data.devices.each { device ->
|
||||
if (device.smartDeviceId && hasDevice[device.smartDeviceId]) {
|
||||
debug "found device ${device.uuid} with smartDeviceId ${device.smartDeviceId}"
|
||||
state.vendorDevices[device.smartDeviceId] = getDeviceInfo(device)
|
||||
}
|
||||
debug "has device: ${device.uuid} ${device.name} ${device.type}"
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "devices error ${e}"
|
||||
}
|
||||
|
||||
selectedCapabilities.each { capability ->
|
||||
debug "checking devices for capability ${capability}"
|
||||
createDevices(settings["${capability}Capability"])
|
||||
}
|
||||
if (pleaseCreateAppDevice)
|
||||
createAppDevice()
|
||||
|
||||
return dynamicPage(name: "devicesPage", title: "Octoblu Things", install: true) {
|
||||
section {
|
||||
paragraph title: "Please press 'Done' to finish setup", "and subscribe to SmartThing events"
|
||||
paragraph title: "My Octoblu UUID:", "${state.vendorUuid}"
|
||||
paragraph title: "My SmartThings in Octobu (${state.vendorDevices.size()}):", getDevInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def createDevices(smartDevices) {
|
||||
|
||||
smartDevices.each { smartDevice ->
|
||||
def commands = [
|
||||
[ "name": "app-get-value" ],
|
||||
[ "name": "app-get-state" ],
|
||||
[ "name": "app-get-device" ],
|
||||
[ "name": "app-get-events" ]
|
||||
]
|
||||
|
||||
smartDevice.supportedCommands.each { command ->
|
||||
if (command.arguments.size()>0) {
|
||||
commands.push([ "name": command.name, "args": command.arguments ])
|
||||
} else {
|
||||
commands.push([ "name": command.name ])
|
||||
}
|
||||
}
|
||||
|
||||
debug "creating device for ${smartDevice.id}"
|
||||
|
||||
def schemas = [
|
||||
"version": "2.0.0",
|
||||
"message": [:]
|
||||
]
|
||||
|
||||
commands.each { command ->
|
||||
schemas."message"."$command.name" = [
|
||||
"type": "object",
|
||||
"properties": [
|
||||
"smartDeviceId": [
|
||||
"type": "string",
|
||||
"readOnly": true,
|
||||
"default": "$smartDevice.id",
|
||||
"x-schema-form": [
|
||||
"condition": "false"
|
||||
]
|
||||
],
|
||||
"command": [
|
||||
"type": "string",
|
||||
"readOnly": true,
|
||||
"default": "$command.name",
|
||||
"enum": ["$command.name"],
|
||||
"x-schema-form": [
|
||||
"condition": "false"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
if (command.args) {
|
||||
schemas."message"."$command.name"."properties"."args" = [
|
||||
"type": "object",
|
||||
"title": "Arguments",
|
||||
"properties": [:]
|
||||
]
|
||||
|
||||
command.args.each { arg ->
|
||||
def argLower = "$arg"
|
||||
argLower = argLower.toLowerCase()
|
||||
if (argLower == "color_map") {
|
||||
schemas."message"."$command.name"."properties"."args"."properties"."$argLower" = [
|
||||
"type": "object",
|
||||
"properties": [
|
||||
"hex": [
|
||||
"type": "string"
|
||||
],
|
||||
"level": [
|
||||
"type": "number"
|
||||
]
|
||||
]
|
||||
]
|
||||
} else {
|
||||
schemas."message"."$command.name"."properties"."args"."properties"."$argLower" = [
|
||||
"type": "$argLower"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug "UPDATED message schema: ${schemas}"
|
||||
|
||||
def deviceProperties = [
|
||||
"schemas": schemas,
|
||||
"needsSetup": false,
|
||||
"online": true,
|
||||
"name": "${smartDevice.displayName}",
|
||||
"smartDeviceId": "${smartDevice.id}",
|
||||
"logo": "https://i.imgur.com/TsXefbK.png",
|
||||
"owner": "${state.vendorUuid}",
|
||||
"configureWhitelist": [],
|
||||
"discoverWhitelist": ["${state.vendorUuid}"],
|
||||
"receiveWhitelist": [],
|
||||
"sendWhitelist": [],
|
||||
"type": "device:${smartDevice.name.replaceAll('\\s','-').toLowerCase()}",
|
||||
"category": "smart-things",
|
||||
"meshblu": [
|
||||
"forwarders": [
|
||||
"received": [[
|
||||
"url": getApiServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/message",
|
||||
"method": "POST",
|
||||
"type": "webhook"
|
||||
]]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
updatePermissions(deviceProperties, smartDevice.id)
|
||||
def params = [
|
||||
uri: apiUrl() + "devices",
|
||||
headers: ["Authorization": "Bearer ${state.vendorBearerToken}"],
|
||||
body: groovy.json.JsonOutput.toJson(deviceProperties)
|
||||
]
|
||||
|
||||
try {
|
||||
|
||||
if (!state.vendorDevices[smartDevice.id]) {
|
||||
debug "creating new device for ${smartDevice.id}"
|
||||
httpPostJson(params) { response ->
|
||||
state.vendorDevices[smartDevice.id] = getDeviceInfo(response.data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
params.uri = params.uri + "/${state.vendorDevices[smartDevice.id].uuid}"
|
||||
debug "the device ${smartDevice.id} has already been created, updating ${params.uri}"
|
||||
httpPutJson(params) { response ->
|
||||
resetVendorDeviceToken(smartDevice.id);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log.error "unable to create new device ${e}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def createAppDevice() {
|
||||
def commands = [
|
||||
[ "name": "app-get-location" ],
|
||||
[ "name": "app-get-devices" ],
|
||||
[ "name": "app-set-mode" ],
|
||||
]
|
||||
|
||||
def schemas = [
|
||||
"version": "2.0.0",
|
||||
"message": [:]
|
||||
]
|
||||
|
||||
commands.each { command ->
|
||||
schemas."message"."$command.name" = [
|
||||
"type": "object",
|
||||
"properties": [
|
||||
"command": [
|
||||
"type": "string",
|
||||
"readOnly": true,
|
||||
"default": "$command.name",
|
||||
"enum": ["$command.name"],
|
||||
"x-schema-form": [
|
||||
"condition": "false"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
schemas."message"."app-set-mode"."properties"."args" = [
|
||||
"type": "object",
|
||||
"title": "Arguments",
|
||||
"properties": [
|
||||
"mode": [
|
||||
"type": "string"
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
def deviceProperties = [
|
||||
"schemas": schemas,
|
||||
"needsSetup": false,
|
||||
"online": true,
|
||||
"name": "${location.name} SmartApp",
|
||||
"smartDeviceId": "${app.id}",
|
||||
"logo": "https://i.imgur.com/TsXefbK.png",
|
||||
"owner": "${state.vendorUuid}",
|
||||
"configureWhitelist": [],
|
||||
"discoverWhitelist": ["${state.vendorUuid}"],
|
||||
"receiveWhitelist": [],
|
||||
"sendWhitelist": [],
|
||||
"type": "device:smart-things-app",
|
||||
"category": "smart-things",
|
||||
"meshblu": [
|
||||
"forwarders": [
|
||||
"received": [[
|
||||
"url": getApiServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/app",
|
||||
"method": "POST",
|
||||
"type": "webhook"
|
||||
]]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
updatePermissions(deviceProperties, app.id)
|
||||
def params = [
|
||||
uri: apiUrl() + "devices",
|
||||
headers: ["Authorization": "Bearer ${state.vendorBearerToken}"],
|
||||
body: groovy.json.JsonOutput.toJson(deviceProperties)
|
||||
]
|
||||
|
||||
debug "creating app device!"
|
||||
debug params.body
|
||||
try {
|
||||
|
||||
// debug params
|
||||
if (!state.vendorDevices[app.id]) {
|
||||
debug "creating new app device for ${app.id}"
|
||||
httpPostJson(params) { response ->
|
||||
state.vendorDevices[app.id] = getDeviceInfo(response.data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
params.uri = params.uri + "/${state.vendorDevices[app.id].uuid}"
|
||||
debug "the app device ${app.id} has already been created, updating ${params.uri}"
|
||||
httpPutJson(params) { response ->
|
||||
resetVendorDeviceToken(app.id);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log.error "unable to create new device ${e}"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def updatePermissions(newDevice, id) {
|
||||
def device = state.vendorDevices[id]
|
||||
if (!device) return
|
||||
newDevice.configureWhitelist = device.configureWhitelist
|
||||
newDevice.discoverWhitelist = device.discoverWhitelist
|
||||
newDevice.receiveWhitelist = device.receiveWhitelist
|
||||
newDevice.sendWhitelist = device.sendWhitelist
|
||||
}
|
||||
|
||||
def getDeviceInfo(device) {
|
||||
return [
|
||||
"uuid": device.uuid,
|
||||
"token": device.token,
|
||||
"configureWhitelist": device.configureWhitelist,
|
||||
"discoverWhitelist": device.discoverWhitelist,
|
||||
"receiveWhitelist": device.receiveWhitelist,
|
||||
"sendWhitelist": device.sendWhitelist,
|
||||
]
|
||||
}
|
||||
|
||||
def resetVendorDeviceToken(smartDeviceId) {
|
||||
def deviceUUID = state.vendorDevices[smartDeviceId].uuid
|
||||
if (!deviceUUID) {
|
||||
debug "no device uuid in resetVendorDeviceToken?"
|
||||
return
|
||||
}
|
||||
debug "getting new token for ${smartDeviceId}/${deviceUUID}"
|
||||
def postParams = [
|
||||
uri: apiUrl() + "devices/${deviceUUID}/token",
|
||||
headers: ["Authorization": "Bearer ${state.vendorBearerToken}"]]
|
||||
try {
|
||||
httpPost(postParams) { response ->
|
||||
state.vendorDevices[smartDeviceId] = getDeviceInfo(response.data)
|
||||
debug "got new token for ${smartDeviceId}/${deviceUUID}"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "unable to get new token ${e}"
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
debug "Updated with settings: ${settings}"
|
||||
def subscribed = [:]
|
||||
selectedCapabilities.each{ capability ->
|
||||
settings."${capability}Capability".each { thing ->
|
||||
if (subscribed[thing.id]) {
|
||||
return
|
||||
}
|
||||
subscribed[thing.id] = true
|
||||
thing.supportedAttributes.each { attribute ->
|
||||
debug "subscribe to attribute ${attribute.name}"
|
||||
subscribe thing, attribute.name, eventForward
|
||||
}
|
||||
thing.supportedCommands.each { command ->
|
||||
debug "subscribe to command ${command.name}"
|
||||
subscribeToCommand thing, command.name, eventForward
|
||||
}
|
||||
debug "subscribed to thing ${thing.id}"
|
||||
}
|
||||
}
|
||||
cleanUpTokens()
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
def cleanUpTokens() {
|
||||
|
||||
if (state.vendorToken) {
|
||||
def params = [
|
||||
uri: apiUrl() + "devices/${state.vendorUuid}/tokens/${state.vendorToken}",
|
||||
headers: ["Authorization": "Bearer ${state.vendorBearerToken}"]
|
||||
]
|
||||
|
||||
debug "deleting url ${params.uri}"
|
||||
try {
|
||||
httpDelete(params) { response ->
|
||||
debug "revoked token for ${state.vendorUuid}...?"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "token delete error ${e}"
|
||||
}
|
||||
}
|
||||
|
||||
state.vendorBearerToken = null
|
||||
state.vendorUuid = null
|
||||
state.vendorToken = null
|
||||
|
||||
if (state.vendorOAuthToken) {
|
||||
def params = [
|
||||
uri: apiUrl() + "devices/${state.vendorOAuthUuid}",
|
||||
headers: [
|
||||
"meshblu_auth_uuid": state.vendorOAuthUuid,
|
||||
"meshblu_auth_token": state.vendorOAuthToken
|
||||
]
|
||||
]
|
||||
|
||||
debug "deleting url ${params.uri}"
|
||||
try {
|
||||
httpDelete(params) { response ->
|
||||
debug "deleted oauth device for ${state.vendorOAuthUuid}...?"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "oauth token delete error ${e}"
|
||||
}
|
||||
}
|
||||
|
||||
state.vendorOAuthUuid = null
|
||||
state.vendorOAuthToken = null
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
def getOauthCode() {
|
||||
// revokeAccessToken()
|
||||
// state.accessToken = createAccessToken()
|
||||
debug "generated app access token ${state.accessToken}"
|
||||
|
||||
def postParams = [
|
||||
uri: getVendorTokenPath(),
|
||||
body: [
|
||||
client_id: state.vendorOAuthUuid,
|
||||
client_secret: state.vendorOAuthToken,
|
||||
grant_type: "authorization_code",
|
||||
code: params.code
|
||||
]
|
||||
]
|
||||
|
||||
def style = "<style type='text/css'>body{font-size:2em;padding:1em}</style>"
|
||||
def startBody = "<html>${style}<body>"
|
||||
def endBody = "</body></html>"
|
||||
def goodResponse = "${startBody}<h1>Received Octoblu Token!</h1><h2>Press 'Done' to finish setup.</h2>${endBody}"
|
||||
def badResponse = "${startBody}<h1>Something went wrong...</h1><h2>PANIC!</h2>${endBody}"
|
||||
debug "authorizeToken with postParams ${postParams}"
|
||||
|
||||
try {
|
||||
httpPost(postParams) { response ->
|
||||
debug "response: ${response.data}"
|
||||
state.vendorBearerToken = response.data.access_token
|
||||
def bearer = new String((new Base64()).decode(state.vendorBearerToken)).split(":")
|
||||
state.vendorUuid = bearer[0]
|
||||
state.vendorToken = bearer[1]
|
||||
|
||||
debug "have octoblu tokens ${state.vendorBearerToken}"
|
||||
render contentType: 'text/html', data: (state.vendorBearerToken ? goodResponse : badResponse)
|
||||
}
|
||||
} catch(e) {
|
||||
log.error "second leg oauth error ${e}"
|
||||
render contentType: 'text/html', data: badResponse
|
||||
}
|
||||
}
|
||||
|
||||
def getEventData(evt) {
|
||||
return [
|
||||
"date" : evt.date,
|
||||
"id" : evt.id,
|
||||
"data" : evt.data,
|
||||
"description" : evt.description,
|
||||
"descriptionText" : evt.descriptionText,
|
||||
"displayName" : evt.displayName,
|
||||
"deviceId" : evt.deviceId,
|
||||
"hubId" : evt.hubId,
|
||||
"installedSmartAppId" : evt.installedSmartAppId,
|
||||
"isoDate" : evt.isoDate,
|
||||
"isDigital" : evt.isDigital(),
|
||||
"isPhysical" : evt.isPhysical(),
|
||||
"isStateChange" : evt.isStateChange(),
|
||||
"locationId" : evt.locationId,
|
||||
"name" : evt.name,
|
||||
"source" : evt.source,
|
||||
"unit" : evt.unit,
|
||||
"value" : evt.value,
|
||||
"category" : "event",
|
||||
"type" : "device:smart-thing"
|
||||
]
|
||||
}
|
||||
|
||||
def eventForward(evt) {
|
||||
def eventData = [ "devices" : [ "*" ], "payload" : getEventData(evt) ]
|
||||
|
||||
debug "sending event: ${groovy.json.JsonOutput.toJson(eventData)}"
|
||||
|
||||
def vendorDevice = state.vendorDevices[evt.deviceId]
|
||||
if (!vendorDevice) {
|
||||
log.error "aborting, vendor device for ${evt.deviceId} doesn't exist?"
|
||||
return
|
||||
}
|
||||
|
||||
debug "using device ${vendorDevice}"
|
||||
|
||||
def postParams = [
|
||||
uri: apiUrl() + "messages",
|
||||
headers: [
|
||||
"meshblu_auth_uuid": vendorDevice.uuid,
|
||||
"meshblu_auth_token": vendorDevice.token
|
||||
],
|
||||
body: groovy.json.JsonOutput.toJson(eventData)
|
||||
]
|
||||
|
||||
try {
|
||||
httpPostJson(postParams) { response ->
|
||||
debug "sent off device event"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "unable to send device event ${e}"
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
def postMessage() {
|
||||
debug("received message data ${request.JSON}")
|
||||
def foundDevice = false
|
||||
selectedCapabilities.each{ capability ->
|
||||
settings."${capability}Capability".each { thing ->
|
||||
if (!foundDevice && thing.id == request.JSON.smartDeviceId) {
|
||||
def vendorDevice = state.vendorDevices[thing.id]
|
||||
foundDevice = true
|
||||
if (vendorDevice.uuid == request.JSON.fromUuid) {
|
||||
log.error "aborting message from self"
|
||||
return
|
||||
}
|
||||
|
||||
if (!request.JSON.command.startsWith("app-")) {
|
||||
def args = []
|
||||
if (request.JSON.args) {
|
||||
request.JSON.args.each { k, v ->
|
||||
args.push(v)
|
||||
}
|
||||
}
|
||||
|
||||
debug "command being sent: ${request.JSON.command}\targs to be sent: ${args}"
|
||||
thing."${request.JSON.command}"(*args)
|
||||
} else {
|
||||
debug "calling internal command ${request.JSON.command}"
|
||||
def commandData = [:]
|
||||
switch (request.JSON.command) {
|
||||
case "app-get-value":
|
||||
debug "got command value"
|
||||
thing.supportedAttributes.each { attribute ->
|
||||
commandData[attribute.name] = thing.latestValue(attribute.name)
|
||||
}
|
||||
break
|
||||
case "app-get-state":
|
||||
debug "got command state"
|
||||
thing.supportedAttributes.each { attribute ->
|
||||
commandData[attribute.name] = thing.latestState(attribute.name)?.value
|
||||
}
|
||||
break
|
||||
case "app-get-device":
|
||||
debug "got command device"
|
||||
commandData = [
|
||||
"id" : thing.id,
|
||||
"displayName" : thing.displayName,
|
||||
"name" : thing.name,
|
||||
"label" : thing.label,
|
||||
"capabilities" : thing.capabilities.collect{ thingCapability -> return thingCapability.name },
|
||||
"supportedAttributes" : thing.supportedAttributes.collect{ attribute -> return attribute.name },
|
||||
"supportedCommands" : thing.supportedCommands.collect{ command -> return ["name" : command.name, "arguments" : command.arguments ] }
|
||||
]
|
||||
break
|
||||
case "app-get-events":
|
||||
debug "got command events"
|
||||
commandData.events = []
|
||||
thing.events().each { event ->
|
||||
commandData.events.push(getEventData(event))
|
||||
}
|
||||
break
|
||||
default:
|
||||
commandData.error = "unknown command"
|
||||
debug "unknown command ${request.JSON.command}"
|
||||
}
|
||||
commandData.command = request.JSON.command
|
||||
debug "with vendorDevice ${vendorDevice} for ${groovy.json.JsonOutput.toJson(commandData)}"
|
||||
|
||||
def postParams = [
|
||||
uri: apiUrl() + "messages",
|
||||
headers: ["meshblu_auth_uuid": vendorDevice.uuid, "meshblu_auth_token": vendorDevice.token],
|
||||
body: groovy.json.JsonOutput.toJson([ "devices" : [ "*" ], "payload" : commandData ])
|
||||
]
|
||||
|
||||
debug "posting params ${postParams}"
|
||||
|
||||
try {
|
||||
debug "calling httpPostJson!"
|
||||
httpPostJson(postParams) { response ->
|
||||
debug "sent off command result"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "unable to send command result ${e}"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
def postApp() {
|
||||
debug("received app data ${request.JSON}")
|
||||
if (state.vendorDevices[app.id].uuid == request.JSON.fromUuid) {
|
||||
log.error "aborting message from self"
|
||||
return
|
||||
}
|
||||
def args = []
|
||||
if (request.JSON.args) {
|
||||
request.JSON.args.each { k, v ->
|
||||
args.push(v)
|
||||
}
|
||||
}
|
||||
|
||||
def commandData = [:]
|
||||
|
||||
switch (request.JSON.command) {
|
||||
case "app-get-location":
|
||||
debug "got command location"
|
||||
|
||||
def modes = []
|
||||
location.modes.each { mode ->
|
||||
modes.push([
|
||||
"id" : mode.id,
|
||||
"name" : mode.name
|
||||
])
|
||||
}
|
||||
|
||||
def hubs = []
|
||||
location.hubs.each { hub ->
|
||||
debug "hub : ${hub}"
|
||||
hubs.push([
|
||||
"firmwareVersionString" : hub.firmwareVersionString,
|
||||
"id" : hub.id,
|
||||
"localIP" : hub.localIP,
|
||||
"localSrvPortTCP" : hub.localSrvPortTCP,
|
||||
"name" : hub.name,
|
||||
"type" : hub.type,
|
||||
"zigbeeEui" : hub.zigbeeEui,
|
||||
"zigbeeId" : hub.zigbeeId
|
||||
])
|
||||
}
|
||||
|
||||
commandData = [
|
||||
"contactBookEnabled" : location.contactBookEnabled,
|
||||
"id" : location.id,
|
||||
"latitude" : location.latitude,
|
||||
"longitude" : location.longitude,
|
||||
"temperatureScale" : location.temperatureScale,
|
||||
"timeZone" : location.timeZone.getID(),
|
||||
"zipCode" : location.zipCode,
|
||||
"mode" : location.mode,
|
||||
"modes" : modes,
|
||||
"hubs" : hubs
|
||||
]
|
||||
|
||||
debug "copied location!"
|
||||
debug commandData
|
||||
break
|
||||
|
||||
case "app-get-devices":
|
||||
debug "got command devices"
|
||||
commandData.devices = state.vendorDevices.collect { k, v -> [ "smartDeviceId" : k, "uuid" : v.uuid ] }
|
||||
break
|
||||
|
||||
case "app-set-mode":
|
||||
location.setMode(*args)
|
||||
commandData.mode = args[0]
|
||||
break
|
||||
|
||||
default:
|
||||
commandData.error = "unknown command"
|
||||
debug "unknown command ${request.JSON.command}"
|
||||
}
|
||||
commandData.command = request.JSON.command
|
||||
debug "sending ${commandData}"
|
||||
|
||||
def vendorDevice = state.vendorDevices[app.id]
|
||||
debug "with vendorDevice ${vendorDevice} for ${groovy.json.JsonOutput.toJson(commandData)}"
|
||||
|
||||
def postParams = [
|
||||
uri: apiUrl() + "messages",
|
||||
headers: [ "meshblu_auth_uuid" : vendorDevice.uuid, "meshblu_auth_token" : vendorDevice.token ],
|
||||
body: groovy.json.JsonOutput.toJson([ "devices" : [ "*" ], "payload" : commandData ])
|
||||
]
|
||||
|
||||
debug "posting params ${postParams}"
|
||||
|
||||
try {
|
||||
debug "calling httpPostJson!"
|
||||
httpPostJson(postParams) { response ->
|
||||
debug "sent off command result"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "unable to send command result ${e}"
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
private debug(logStr) {
|
||||
if (USE_DEBUG)
|
||||
log.debug logStr
|
||||
}
|
||||
|
||||
String toQueryString(Map m) {
|
||||
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
|
||||
}
|
||||
|
||||
def initialize()
|
||||
{
|
||||
debug "Initialized with settings: ${settings}"
|
||||
}
|
||||
|
||||
def uninstalled()
|
||||
{
|
||||
debug "In uninstalled ${state.removeDevices}"
|
||||
if (state.removeDevices) {
|
||||
state.vendorDevices.each { k, device ->
|
||||
def params = [
|
||||
uri: apiUrl() + "devices/${device.uuid}",
|
||||
headers: [ "meshblu_auth_uuid" : device.uuid, "meshblu_auth_token" : device.token ],
|
||||
]
|
||||
|
||||
debug "deleting url ${params.uri}"
|
||||
try {
|
||||
httpDelete(params) { response ->
|
||||
debug "delete device ${device.uuid}"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "token delete error ${e}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
debug "Installed with settings: ${settings}"
|
||||
state.installed = true
|
||||
}
|
||||
|
||||
private Boolean canInstallLabs()
|
||||
{
|
||||
return hasAllHubsOver("000.011.00603")
|
||||
}
|
||||
|
||||
private Boolean hasAllHubsOver(String desiredFirmware)
|
||||
{
|
||||
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
||||
}
|
||||
|
||||
private List getRealHubFirmwareVersions()
|
||||
{
|
||||
return location.hubs*.firmwareVersionString.findAll { it }
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Medicine Management - Contact Sensor
|
||||
*
|
||||
* Copyright 2016 Jim Mangione
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Logic:
|
||||
* --- Send notification at the medicine reminder time IF draw wasn't alread opened in past 60 minutes
|
||||
* --- If draw still isn't open 10 minutes AFTER reminder time, LED will turn RED.
|
||||
* --- ----- Once draw IS open, LED will return back to it's original color
|
||||
*
|
||||
*/
|
||||
import groovy.time.TimeCategory
|
||||
|
||||
definition(
|
||||
name: "Medicine Management - Contact Sensor",
|
||||
namespace: "MangioneImagery",
|
||||
author: "Jim Mangione",
|
||||
description: "This supports devices with capabilities of ContactSensor and ColorControl (LED). It sends an in-app and ambient light notification if you forget to open the drawer or cabinet where meds are stored. A reminder will be set to a single time per day. If the draw or cabinet isn't opened within 60 minutes of that reminder, an in-app message will be sent. If the draw or cabinet still isn't opened after an additional 10 minutes, then an LED light turns red until the draw or cabinet is opened",
|
||||
category: "Health & Wellness",
|
||||
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("My Medicine Draw/Cabinet"){
|
||||
input "deviceContactSensor", "capability.contactSensor", title: "Opened Sensor"
|
||||
}
|
||||
|
||||
section("Remind me to take my medicine at"){
|
||||
input "reminderTime", "time", title: "Time"
|
||||
}
|
||||
|
||||
// NOTE: Use REAL device - virtual device causes compilation errors
|
||||
section("My LED Light"){
|
||||
input "deviceLight", "capability.colorControl", title: "Smart light"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
unsubscribe()
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
|
||||
// will stop LED notification incase it was set by med reminder
|
||||
subscribe(deviceContactSensor, "contact", contactHandler)
|
||||
|
||||
// how many minutes to look in the past from the reminder time, for an open draw
|
||||
state.minutesToCheckOpenDraw = 60
|
||||
|
||||
// is true when LED notification is set after exceeding 10 minutes past reminder time
|
||||
state.ledNotificationTriggered = false
|
||||
|
||||
// Set a timer to run once a day to notify if draw wasn't opened yet
|
||||
schedule(reminderTime, checkOpenDrawInPast)
|
||||
|
||||
}
|
||||
|
||||
// Should turn off any LED notification on OPEN state
|
||||
def contactHandler(evt){
|
||||
if (evt.value == "open") {
|
||||
// if LED notification triggered, reset it.
|
||||
log.debug "Cabinet opened"
|
||||
if (state.ledNotificationTriggered) {
|
||||
resetLEDNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the draw was NOT opened within 60 minutes of the timer send notification out.
|
||||
def checkOpenDrawInPast(){
|
||||
log.debug "Checking past 60 minutes of activity from $reminderTime"
|
||||
|
||||
// check activity of sensor for past 60 minutes for any OPENED status
|
||||
def cabinetOpened = isOpened(state.minutesToCheckOpenDraw)
|
||||
log.debug "Cabinet found opened: $cabinetOpened"
|
||||
|
||||
// if it's opened, then do nothing and assume they took their meds
|
||||
if (!cabinetOpened) {
|
||||
sendNotification("Hi, please remember to take your meds in the cabinet")
|
||||
|
||||
// if no open activity, send out notification and set new reminder
|
||||
def reminderTimePlus10 = new Date(now() + (10 * 60000))
|
||||
|
||||
// needs to be scheduled if draw wasn't already opened
|
||||
runOnce(reminderTimePlus10, checkOpenDrawAfterReminder)
|
||||
}
|
||||
}
|
||||
|
||||
// If the draw was NOT opened after 10 minutes past reminder, use LED notification
|
||||
def checkOpenDrawAfterReminder(){
|
||||
log.debug "Checking additional 10 minutes of activity from $reminderTime"
|
||||
|
||||
// check activity of sensor for past 10 minutes for any OPENED status
|
||||
def cabinetOpened = isOpened(10)
|
||||
|
||||
log.debug "Cabinet found opened: $cabinetOpened"
|
||||
|
||||
// if no open activity, blink lights
|
||||
if (!cabinetOpened) {
|
||||
log.debug "Set LED to Notification color"
|
||||
setLEDNotification()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Helper function for sending out an app notification
|
||||
def sendNotification(msg){
|
||||
log.debug "Message Sent: $msg"
|
||||
sendPush(msg)
|
||||
}
|
||||
|
||||
// Check if the sensor has been opened since the minutes entered
|
||||
// Return true if opened found, else false.
|
||||
def isOpened(minutes){
|
||||
// query last X minutes of activity log
|
||||
def previousDateTime = new Date(now() - (minutes * 60000))
|
||||
|
||||
// capture all events recorded
|
||||
def evts = deviceContactSensor.eventsSince(previousDateTime)
|
||||
def cabinetOpened = false
|
||||
if (evts.size() > 0) {
|
||||
evts.each{
|
||||
if(it.value == "open") {
|
||||
cabinetOpened = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cabinetOpened
|
||||
}
|
||||
|
||||
// Saves current color and sets the light to RED
|
||||
def setLEDNotification(){
|
||||
|
||||
state.ledNotificationTriggered = true
|
||||
|
||||
// turn light back off when reset is called if it was originally off
|
||||
state.ledState = deviceLight.currentValue("switch")
|
||||
|
||||
// set light to RED and store original color until stopped
|
||||
state.origColor = deviceLight.currentValue("hue")
|
||||
deviceLight.on()
|
||||
deviceLight.setHue(100)
|
||||
|
||||
log.debug "LED set to RED. Original color stored: $state.origColor"
|
||||
|
||||
}
|
||||
|
||||
// Sets the color back to the original saved color
|
||||
def resetLEDNotification(){
|
||||
|
||||
state.ledNotificationTriggered = false
|
||||
|
||||
// return color to original
|
||||
log.debug "Reset LED color to: $state.origColor"
|
||||
if (state.origColor != null) {
|
||||
deviceLight.setHue(state.origColor)
|
||||
}
|
||||
|
||||
// if the light was turned on just for the notification, turn it back off now
|
||||
if (state.ledState == "off") {
|
||||
deviceLight.off()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* Medicine Management - Temp-Motion
|
||||
*
|
||||
* Copyright 2016 Jim Mangione
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Logic:
|
||||
* --- If temp > threshold set, send notification
|
||||
* --- Send in-app notification at the medicine reminder time if no motion is detected in past 60 minutes
|
||||
* --- If motion still isn't detected 10 minutes AFTER reminder time, LED will turn RED
|
||||
* --- ----- Once motion is detected, LED will turn back to it's original color
|
||||
*/
|
||||
import groovy.time.TimeCategory
|
||||
|
||||
definition(
|
||||
name: "Medicine Management - Temp-Motion",
|
||||
namespace: "MangioneImagery",
|
||||
author: "Jim Mangione",
|
||||
description: "This only supports devices with capabilities TemperatureMeasurement, AccelerationSensor and ColorControl (LED). Supports two use cases. First, will notifies via in-app if the fridge where meds are stored exceeds a temperature threshold set in degrees. Secondly, sends an in-app and ambient light notification if you forget to take your meds by sensing movement of the medicine box in the fridge. A reminder will be set to a single time per day. If the box isn't moved within 60 minutes of that reminder, an in-app message will be sent. If the box still isn't moved after an additional 10 minutes, then an LED light turns red until the box is moved",
|
||||
category: "Health & Wellness",
|
||||
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("My Medicine in the Refrigerator"){
|
||||
input "deviceAccelerationSensor", "capability.accelerationSensor", required: true, multiple: false, title: "Movement"
|
||||
input "deviceTemperatureMeasurement", "capability.temperatureMeasurement", required: true, multiple: false, title: "Temperature"
|
||||
}
|
||||
|
||||
section("Temperature Threshold"){
|
||||
input "tempThreshold", "number", title: "Temperature Threshold"
|
||||
}
|
||||
|
||||
section("Remind me to take my medicine at"){
|
||||
input "reminderTime", "time", title: "Time"
|
||||
}
|
||||
|
||||
// NOTE: Use REAL device - virtual device causes compilation errors
|
||||
section("My LED Light"){
|
||||
input "deviceLight", "capability.colorControl", title: "Smart light"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
unsubscribe()
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
// will notify when temp exceeds max
|
||||
subscribe(deviceTemperatureMeasurement, "temperature", tempHandler)
|
||||
|
||||
// will stop LED notification incase it was set by med reminder
|
||||
subscribe(deviceAccelerationSensor, "acceleration.active", motionHandler)
|
||||
|
||||
// how many minutes to look in the past from the reminder time
|
||||
state.minutesToCheckPriorToReminder = 60
|
||||
|
||||
// Set a timer to run once a day to notify if draw wasn't opened yet
|
||||
schedule(reminderTime, checkMotionInPast)
|
||||
}
|
||||
|
||||
|
||||
// If temp > 39 then send an app notification out.
|
||||
def tempHandler(evt){
|
||||
if (evt.doubleValue > tempThreshold) {
|
||||
log.debug "Fridge temp of $evt.value exceeded threshold"
|
||||
sendNotification("WARNING: Fridge temp is $evt.value with threshold of $tempThreshold")
|
||||
}
|
||||
}
|
||||
|
||||
// Should turn off any LED notification once motion detected
|
||||
def motionHandler(evt){
|
||||
// always call out to stop any possible LED notification
|
||||
log.debug "Medication moved. Send stop LED notification"
|
||||
resetLEDNotification()
|
||||
}
|
||||
|
||||
// If no motion detected within 60 minutes of the timer send notification out.
|
||||
def checkMotionInPast(){
|
||||
log.debug "Checking past 60 minutes of activity from $reminderTime"
|
||||
|
||||
// check activity of sensor for past 60 minutes for any OPENED status
|
||||
def movement = isMoved(state.minutesToCheckPriorToReminder)
|
||||
log.debug "Motion found: $movement"
|
||||
|
||||
// if there was movement, then do nothing and assume they took their meds
|
||||
if (!movement) {
|
||||
sendNotification("Hi, please remember to take your meds in the fridge")
|
||||
|
||||
// if no movement, send out notification and set new reminder
|
||||
def reminderTimePlus10 = new Date(now() + (10 * 60000))
|
||||
|
||||
// needs to be scheduled if draw wasn't already opened
|
||||
runOnce(reminderTimePlus10, checkMotionAfterReminder)
|
||||
}
|
||||
}
|
||||
|
||||
// If still no movement after 10 minutes past reminder, use LED notification
|
||||
def checkMotionAfterReminder(){
|
||||
log.debug "Checking additional 10 minutes of activity from $reminderTime"
|
||||
|
||||
// check activity of sensor for past 10 minutes for any OPENED status
|
||||
def movement = isMoved(10)
|
||||
|
||||
log.debug "Motion found: $movement"
|
||||
|
||||
// if no open activity, blink lights
|
||||
if (!movement) {
|
||||
log.debug "Notify LED API"
|
||||
setLEDNotification()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Helper function for sending out an app notification
|
||||
def sendNotification(msg){
|
||||
log.debug "Message Sent: $msg"
|
||||
sendPush(msg)
|
||||
}
|
||||
|
||||
// Check if the accelerometer has been activated since the minutes entered
|
||||
// Return true if active, else false.
|
||||
def isMoved(minutes){
|
||||
// query last X minutes of activity log
|
||||
def previousDateTime = new Date(now() - (minutes * 60000))
|
||||
|
||||
// capture all events recorded
|
||||
def evts = deviceAccelerationSensor.eventsSince(previousDateTime)
|
||||
def motion = false
|
||||
if (evts.size() > 0) {
|
||||
evts.each{
|
||||
if(it.value == "active") {
|
||||
motion = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return motion
|
||||
}
|
||||
|
||||
// Saves current color and sets the light to RED
|
||||
def setLEDNotification(){
|
||||
|
||||
// turn light back off when reset is called if it was originally off
|
||||
state.ledState = deviceLight.currentValue("switch")
|
||||
|
||||
// set light to RED and store original color until stopped
|
||||
state.origColor = deviceLight.currentValue("hue")
|
||||
deviceLight.on()
|
||||
deviceLight.setHue(100)
|
||||
|
||||
log.debug "LED set to RED. Original color stored: $state.origColor"
|
||||
|
||||
}
|
||||
|
||||
// Sets the color back to the original saved color
|
||||
def resetLEDNotification(){
|
||||
|
||||
// return color to original
|
||||
log.debug "Reset LED color to: $state.origColor"
|
||||
deviceLight.setHue(state.origColor)
|
||||
|
||||
// if the light was turned on just for the notification, turn it back off now
|
||||
if (state.ledState == "off") {
|
||||
deviceLight.off()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user