Compare commits

..

1 Commits

57 changed files with 639 additions and 1138 deletions

View File

@@ -19,7 +19,7 @@ buildscript {
username smartThingsArtifactoryUserName username smartThingsArtifactoryUserName
password smartThingsArtifactoryPassword password smartThingsArtifactoryPassword
} }
url "https://artifactory.smartthings.com/libs-release-local" url "http://artifactory.smartthings.com/libs-release-local"
} }
} }
} }
@@ -27,37 +27,9 @@ buildscript {
repositories { repositories {
mavenLocal() mavenLocal()
jcenter() jcenter()
maven {
credentials {
username smartThingsArtifactoryUserName
password smartThingsArtifactoryPassword
}
url "https://artifactory.smartthings.com/libs-release-local"
}
}
sourceSets {
devicetypes {
groovy {
srcDirs = ['devicetypes']
}
}
smartapps {
groovy {
srcDirs = ['smartapps']
}
}
} }
dependencies { dependencies {
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
devicetypesCompile 'smartthings:appengine-z-wave:0.1.2'
devicetypesCompile 'smartthings:appengine-zigbee:0.1.11'
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
smartappsCompile 'smartthings:appengine-common:0.1.8'
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
smartappsCompile 'org.grails:grails-web:2.3.11'
smartappsCompile 'org.json:json:20140107'
} }
slackSendMessage { slackSendMessage {

View File

@@ -5,9 +5,7 @@ machine:
dependencies: dependencies:
override: override:
- ./gradlew dependencies -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" - echo "Nothing to do."
post:
- ./gradlew compileSmartappsGroovy compileDevicetypesGroovy -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
test: test:
override: override:

View File

@@ -0,0 +1,183 @@
metadata {
definition (name: "Rooflight Controller", namespace: "CR76", author: "Cameron.Reid@Glazingvision.co.uk") {
capability "Refresh"
capability "Polling"
capability "Switch"
capability "Switch level"
attribute "Rooflight", "string"
attribute "Weather", "string"
attribute "Thermostat", "string"
command "Stop"
fingerprint profileId: "0104", inClusters: "0000,0006,0008"
}
// simulator metadata
simulator {
}
// UI tile definitions
tiles {
multiAttributeTile(name:"sliderTile", type: "generic", width:6, height:4) {
tileAttribute("device.Rooflight", key: "PRIMARY_CONTROL") {
attributeState "Open", label:'Open', action: "Switch.off", backgroundColor:"#79b821", nextState:"Closing"
attributeState "Closed", label:'Closed', action: "Switch.on", backgroundColor:"#ffffff", nextState:"Opening"
attributeState "Opening", label:'Opening', backgroundColor:"#79b821"
attributeState "Closing", label:'Closing', backgroundColor:"#ffffff"
}
tileAttribute ("device.Rooflight", key: "SECONDARY_CONTROL") {
attributeState "Open", label: 'Push to close.', nextState:"Closing"
attributeState "Closed", label: 'Push to open.', nextState:"Opening"
attributeState "Opening", label: 'Skylight opening.'
attributeState "Closing", label: 'Skylight closing.'
}
}
standardTile("Close", "device.Rooflight", inactiveLabel: false, decoration: "flat", width:2, height:2 ) {
state "default", label: 'Close', action:"Switch.off", icon:"st.thermostat.thermostat-left"
}
standardTile("Stop", "device.level", inactiveLabel: false, decoration: "flat", width:2, height:2 ) {
state "default", action:"Stop", icon:"st.sonos.stop-btn"
}
standardTile("Open", "device.Rooflight", inactiveLabel: false, decoration: "flat", width:2, height:2 ) {
state "default", label: 'open', action:"Switch.on", icon:"st.thermostat.thermostat-right"
}
standardTile("Rain", "device.Weather", width: 2, height: 2) {
state "No", backgroundColor: "#ffffff"
state "Dry", label: 'Dry', icon:"st.Weather.weather14", backgroundColor: "#ffffff"
state "Raining", label: 'Raining', icon:"st.Weather.weather10", backgroundColor: "#153591"
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width:2, height:2 ) {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main ("sliderTile")
details (["sliderTile","Close","Stop","Open","Rain","refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "Parse description $description"
def name = null
def value = null
if (description?.startsWith("catchall: 0104 0006 38")) {
log.debug "On/Off command received from EP 38 open / close command"
if (description?.endsWith("01 01 0000001000")) {
name = "Rooflight"
value = "Closed"
}
else if (description?.endsWith("01 01 0000001001")) {
name = "Rooflight"
value = "Open"
}
else if (description?.endsWith("01 01 0000001003")) {
name = "Rooflight"
value = "Opening"
}
else if (description?.endsWith("01 01 0000001004")) {
name = "Rooflight"
value = "Closing"
}
}
if (description?.startsWith("catchall: 0104 0006 39")) {
log.debug "On/Off command received from EP 39 is rain sensor connected"
if (description?.endsWith("01 01 0000001000")) {
name = "Weather"
value = "No"
state.tile = 1
}
else if (description?.endsWith("01 01 0000001001")) {
name = "Weather"
value = "Dry"
state.tile = 0
log.debug "${state.tile}"
}
}
if (description?.startsWith("catchall: 0104 0006 40")) {
log.debug "On/Off command received from EP 40 Thermostat"
if (description?.endsWith("01 01 0000001000")){
name = "Switch"
value = "on"
}
else if (description?.endsWith("01 01 0000001001")) {
name = "Switch"
value = "off"
}
}
if (description?.startsWith("catchall: 0104 0006 41")) {
log.debug "On/Off command received from EP 41 rain sensor"
if (description?.endsWith("01 01 0000001000")) {
name = "Weather"
value = "Dry"
}
else if (description?.endsWith("01 01 0000001001")) {
name = "Weather"
value = "Raining"
}
}
if(description?.startsWith('read attr -')) {
def descMap = parseDescriptionAsMap(description) //Get level value.
value = zigbee.convertHexToInt(descMap.value)
log.debug " level = $value"
name = "level"
}
def result = createEvent(name: name, value: value)
log.debug "Parse returned ${result?.descriptionText}"
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) {
map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
// Commands to device
def on() {
log.debug "Rooflight Opening"
def cmd = []
cmd <<"st cmd 0x${device.deviceNetworkId} 0x38 0x0006 0x1 {}"
cmd << "delay 250"
cmd << zigbee.command(0x0008, 0x04, "64")
}
def off() {
log.debug "Rooflight Closing"
def cmd = []
cmd << "st cmd 0x${device.deviceNetworkId} 0x38 0x0006 0x0 {}"
cmd << "delay 250"
cmd << zigbee.command(0x0008, 0x04, "00")
}
def Stop() { //Send stop command in the level control cluster.
log.debug "send stop"
"st cmd 0x${device.deviceNetworkId} 0x38 0x0008 0x07 {}"
}
def poll() {
log.debug "Poll is calling refresh"
refresh()
}
def refresh() {
log.debug "sending refresh command"
log.debug "Tile State: ${state.tile}"
def cmd = []
if("${state.tile}" == "0") { //If rain sensor is connected refresh rain sensor and rooflight position.
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0006 0x0000" // Read on / off attribute at End point 0x38 Rooflight open / closed.
cmd << "delay 250"
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0008 0x0000" // Read Level position at End point 0x38 Rooflight Position.
cmd << "delay 250"
cmd << "st rattr 0x${device.deviceNetworkId} 0x41 0x0006 0x0000" // Read on / off attribute at End point 0x41 Rain sensor.
}
else {
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0006 0x0000" // Read on / off attribute at End point 0x38 Rooflight open / closed.
cmd << "delay 250"
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0008 0x0000" // Read on / off attribute at End point 0x88 Rain sensor.
}
}

View File

@@ -15,7 +15,6 @@
*/ */
metadata { metadata {
definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") { definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Temperature Measurement" capability "Temperature Measurement"

View File

@@ -15,7 +15,6 @@
*/ */
metadata { metadata {
definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") { definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Temperature Measurement" capability "Temperature Measurement"

View File

@@ -15,7 +15,6 @@
*/ */
metadata { metadata {
definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") { definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Temperature Measurement" capability "Temperature Measurement"
} }

View File

@@ -15,8 +15,6 @@
*/ */
metadata { metadata {
definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") { definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
attribute "rain", "number" attribute "rain", "number"
attribute "rainSumHour", "number" attribute "rainSumHour", "number"
attribute "rainSumDay", "number" attribute "rainSumDay", "number"

View File

@@ -33,8 +33,8 @@ metadata {
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
tileAttribute("device.motion", key:"PRIMARY_CONTROL") { tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821") attributeState("inactive", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e") attributeState("active", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
} }
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") { tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
@@ -278,4 +278,4 @@ private encap(physicalgraph.zwave.Command cmd) {
} else { } else {
crc16(cmd) crc16(cmd)
} }
} }

View File

@@ -67,6 +67,12 @@ def parse(String description) {
def resultMap = zigbee.getEvent(description) def resultMap = zigbee.getEvent(description)
if (resultMap) { if (resultMap) {
sendEvent(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 { else {
log.debug "DID NOT PARSE MESSAGE for description : $description" log.debug "DID NOT PARSE MESSAGE for description : $description"
@@ -90,7 +96,15 @@ def setLevel(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
return zigbee.levelRefresh()
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() { def refresh() {
@@ -103,8 +117,6 @@ def poll() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
// minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -67,6 +67,6 @@ def refresh() {
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
parent.poll() parent.pollChild()
} }

View File

@@ -133,7 +133,7 @@ def refresh() {
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
parent.poll() parent.pollChild()
} }
def generateEvent(Map results) { def generateEvent(Map results) {

View File

@@ -57,7 +57,7 @@ metadata {
} }
void installed() { void installed() {
sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false) sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
} }
// parse events into attributes // parse events into attributes

View File

@@ -62,7 +62,7 @@ def parse(description) {
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value" log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
results << createEvent(name: "${map.name}", value: "${map.value}") results << createEvent(name: "${map.name}", value: "${map.value}")
} else { } else {
log.trace "Parsing description" log.trace "Parsing description"
def msg = parseLanMessage(description) def msg = parseLanMessage(description)
if (msg.body) { if (msg.body) {
def contentType = msg.headers["Content-Type"] def contentType = msg.headers["Content-Type"]
@@ -72,13 +72,13 @@ def parse(description) {
log.info "Bridge response: $msg.body" log.info "Bridge response: $msg.body"
} else { } else {
// Sending Bulbs List to parent" // Sending Bulbs List to parent"
if (parent.isInBulbDiscovery()) if (parent.state.inBulbDiscovery)
log.info parent.bulbListHandler(device.hub.id, msg.body) log.info parent.bulbListHandler(device.hub.id, msg.body)
} }
} }
else if (contentType?.contains("xml")) { else if (contentType?.contains("xml")) {
log.debug "HUE BRIDGE ALREADY PRESENT" log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body) parent.hubVerification(device.hub.id, msg.body)
} }
} }
} }

View File

@@ -66,7 +66,7 @@ metadata {
} }
void installed() { void installed() {
sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false) sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
} }
// parse events into attributes // parse events into attributes

View File

@@ -50,7 +50,7 @@ metadata {
} }
void installed() { void installed() {
sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false) sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
} }
// parse events into attributes // parse events into attributes

View File

@@ -55,7 +55,7 @@ metadata {
} }
void installed() { void installed() {
sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false) sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
} }
// parse events into attributes // parse events into attributes

View File

@@ -91,7 +91,7 @@ def parse(String description) {
if (descMap.cluster == "0300") { if (descMap.cluster == "0300") {
if(descMap.attrId == "0000"){ //Hue Attribute if(descMap.attrId == "0000"){ //Hue Attribute
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100) def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
log.debug "Hue value returned is $hueValue" log.debug "Hue value returned is $hueValue"
sendEvent(name: "hue", value: hueValue, displayed:false) sendEvent(name: "hue", value: hueValue, displayed:false)
} }
@@ -203,7 +203,7 @@ def setLevel(value) {
//input Hue Integer values; returns color name for saturation 100% //input Hue Integer values; returns color name for saturation 100%
private getColorName(hueValue){ private getColorName(hueValue){
if(hueValue>100 || hueValue<0) if(hueValue>360 || hueValue<0)
return return
hueValue = Math.round(hueValue / 100 * 360) hueValue = Math.round(hueValue / 100 * 360)

View File

@@ -1,4 +1,4 @@
/* /*
Osram Flex RGBW Light Strip Osram Flex RGBW Light Strip
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
@@ -8,7 +8,7 @@
metadata { metadata {
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") { definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
capability "Color Temperature" capability "Color Temperature"
capability "Actuator" capability "Actuator"
capability "Switch" capability "Switch"
@@ -18,7 +18,7 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Color Control" capability "Color Control"
attribute "colorName", "string" attribute "colorName", "string"
command "setAdjustedColor" command "setAdjustedColor"
@@ -49,7 +49,7 @@ metadata {
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
@@ -118,7 +118,7 @@ def parse(String description) {
} }
} }
else if(descMap.attrId == "0000"){ //Hue Attribute else if(descMap.attrId == "0000"){ //Hue Attribute
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100) def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
log.debug "Hue value returned is $hueValue" log.debug "Hue value returned is $hueValue"
sendEvent(name: "hue", value: hueValue, displayed:false) sendEvent(name: "hue", value: hueValue, displayed:false)
} }
@@ -274,7 +274,7 @@ private getGenericName(value){
//input Hue Integer values; returns color name for saturation 100% //input Hue Integer values; returns color name for saturation 100%
private getColorName(hueValue){ private getColorName(hueValue){
if(hueValue>100 || hueValue<0) if(hueValue>360 || hueValue<0)
return return
hueValue = Math.round(hueValue / 100 * 360) hueValue = Math.round(hueValue / 100 * 360)
@@ -449,7 +449,7 @@ def setColor(value){
def level = hex(value.level * 255 / 100) def level = hex(value.level * 255 / 100)
cmd << zigbeeSetLevel(level) cmd << zigbeeSetLevel(level)
} }
if (value.switch == "off") { if (value.switch == "off") {
cmd << "delay 150" cmd << "delay 150"
cmd << off() cmd << off()

View File

@@ -101,6 +101,12 @@ def parse(String description) {
else { else {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true) 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 { else {
@@ -120,7 +126,15 @@ def on() {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
return zigbee.onOffRefresh()
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() { def refresh() {
@@ -128,10 +142,8 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) zigbee.onOffConfig() + powerConfig() + refresh()
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
} }
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s) //power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)

View File

@@ -101,6 +101,13 @@ def parse(String description) {
map = parseIasMessage(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" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -226,8 +233,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
@@ -278,7 +283,15 @@ private Map getMoistureResult(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
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() { def refresh() {
@@ -292,19 +305,23 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [ def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500", "send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
// battery minReport 30 seconds, maxReportTime 6 hrs by default "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config "send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -105,6 +105,13 @@ def parse(String description) {
map = parseIasMessage(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" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -241,8 +248,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
@@ -289,7 +294,15 @@ private Map getMotionResult(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
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() { def refresh() {
@@ -303,19 +316,24 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [ def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
] ]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity return configCmds + refresh() // send refresh cmds as part of config
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -206,8 +206,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }

View File

@@ -127,6 +127,13 @@ def parse(String description) {
map = parseIasMessage(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 def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) { if (description?.startsWith('enroll request')) {
@@ -306,8 +313,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
@@ -371,7 +376,15 @@ private getAccelerationResult(numValue) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
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() { def refresh() {
@@ -401,16 +414,13 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
log.debug "Configuring Reporting" log.debug "Configuring Reporting"
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
def configCmds = enrollResponse() + def configCmds = enrollResponse() +
zigbee.batteryConfig() + zigbee.batteryConfig() +
zigbee.temperatureConfig(30, 300) + zigbee.temperatureConfig() +
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +

View File

@@ -206,8 +206,6 @@ def getTemperature(value) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }

View File

@@ -92,6 +92,13 @@ def parse(String description) {
map = parseIasMessage(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" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -200,8 +207,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
@@ -241,7 +246,15 @@ private Map getContactResult(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
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() { def refresh() {
@@ -255,19 +268,23 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [ def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500", "send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
// battery minReport 30 seconds, maxReportTime 6 hrs by default "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config "send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -83,6 +83,13 @@ def parse(String description) {
map = parseCustomMessage(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" log.debug "Parse returned $map"
return map ? createEvent(map) : null return map ? createEvent(map) : null
} }
@@ -207,8 +214,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100) def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct) result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
@@ -246,7 +251,14 @@ private Map getHumidityResult(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level 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() def refresh()
@@ -264,19 +276,23 @@ def refresh()
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
def humidityConfigCmds = [ def configCmds = [
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500", "zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}", "zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500" "send 0x${device.deviceNetworkId} 1 1", "delay 500"
] ]
return configCmds + refresh() // send refresh cmds as part of config
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
private hex(value) { private hex(value) {

View File

@@ -1,630 +0,0 @@
/**
* Copyright 2015 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.
*
* Author: SmartThings
* Date: 2013-12-02
*/
metadata {
definition (name: "ZigBee CentraLite Thermostat", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Temperature Measurement"
capability "Thermostat"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Polling"
capability "Battery"
// Custom commands
command "raiseHeatLevel"
command "lowHeatLevel"
command "raiseCoolLevel"
command "lowCoolLevel"
command "setTemperature"
command "setThermostatHoldMode"
command "getPowerSource"
attribute "temperatureScale", "string"
attribute "thermostatHoldMode", "string"
attribute "powerSource", "string"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0020,0201,0202,0204,0B05", outClusters: "000A, 0019"
}
// simulator metadata
simulator { }
tiles(scale: 2) {
multiAttributeTile(name:"summary", type: "thermostat", width: 6, height: 4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temperature", label:'${currentValue}°', unit:"dF",
backgroundColors:[
// Celsius Color Range
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 29, color: "#f1d801"],
[value: 33, color: "#d04e00"],
[value: 36, color: "#bc2323"],
// Fahrenheit Color Range
[value: 40, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 65, color: "#99ff33"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 92, color: "#d04e00"],
[value: 96, color: "#bc2323"]
])
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("default", action: "setTemperature")
}
tileAttribute("device.powerSource", key: "SECONDARY_CONTROL") {
attributeState("default", label:'PowerSrc: ${currentValue}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"",
backgroundColors:[
// Celsius Color Range
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 29, color: "#f1d801"],
[value: 33, color: "#d04e00"],
[value: 36, color: "#bc2323"],
// Fahrenheit Color Range
[value: 40, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 65, color: "#99ff33"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 92, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
standardTile("mode", "device.thermostatMode", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
state "off", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.heating-cooling-off"
state "cool", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.cool", backgroundColor: '#99ddff'
state "heat", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.heat", backgroundColor: '#fd631c'
state "emergencyHeat", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.emergency-heat", backgroundColor: '#E11102'
//state "auto", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.auto", backgroundColor: '#ffff00'
}
standardTile("fanMode", "device.thermostatFanMode", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
state "fanAuto", label:'', action:"thermostat.setThermostatFanMode", icon: "st.thermostat.fan-auto", backgroundColor: '#ffff00'
state "fanOn", label:'', action:"thermostat.setThermostatFanMode", icon: "st.thermostat.fan-on", backgroundColor: '#ffff00'
}
standardTile("holdMode", "device.thermostatHoldMode", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
state "holdOff", label:'Hold Off', action:"setThermostatHoldMode", icon: "st.Lighting.light13", backgroundColor:"#ffffff", nextState:"holdOff"
state "holdOn", label:'Hold On', action:"setThermostatHoldMode", icon: "st.Lighting.light11", backgroundColor:"#ffb3ff", nextState:"holdOn"
}
standardTile("raiseHeatLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
state "raiseHeatLevel", label:' ', action:"raiseHeatLevel", icon:"st.thermostat.thermostat-up"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", height: 1, width: 2, inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue}° Heat', unit:"", backgroundColor:"#fd631c"
}
standardTile("lowHeatLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
state "lowHeatLevel", label:' ', action:"lowHeatLevel", icon:"st.thermostat.thermostat-down"
}
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 4, inactiveLabel: false) {
state "setHeatingSetpoint", action:"thermostat.setHeatingSetpoint", backgroundColor:"#d04e00"
}
standardTile("raiseCoolLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
state "raiseCoolLevel", label:' ', action:"raiseCoolLevel", icon:"st.thermostat.thermostat-up"
}
valueTile("coolingSetpoint", "device.coolingSetpoint", , height: 1, width: 2, inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue}° Cool', unit:"", backgroundColor:"#66ccff"
}
standardTile("lowCoolLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
state "lowCoolLevel", label:' ', action:"lowCoolLevel", icon:"st.thermostat.thermostat-down"
}
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 4, inactiveLabel: false) {
state "setCoolingSetpoint", action:"thermostat.setCoolingSetpoint", backgroundColor: "#003CEC"
}
standardTile("refresh", "device.temperature", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% Battery', unit:""
}
main(["temperature", "summary"])
details(["summary", "holdMode", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "battery", "refresh"])
}
}
def setTemperature(setpoint) {
log.debug "setTemperature() called with setpoint ${setpoint}. "
log.debug "Current temperature: ${device.currentValue("temperature")}. Heat Setpoint: ${device.currentValue("heatingSetpoint")}. Cool Setpoint: ${device.currentValue("coolingSetpoint")}"
def mode = device.currentValue("thermostatMode")
def midpoint
def targetvalue
if (mode == "off") {
log.warn "setTemperature(): this mode: $mode does not allow raiseSetpoint"
return
}
def currentTemp = device.currentValue("temperature")
def deltaTemp = setpoint - currentTemp
log.debug "deltaTemp = ${deltaTemp}"
if (mode == "heat") {
// Change the heat
log.debug "setTemperature(): change the heat temp"
if (deltaTemp < 0) {
lowHeatLevel()
} else if (deltaTemp > 0) {
raiseHeatLevel()
}
} else if (mode == "cool") {
// Change the cool
log.debug "setTemperature(): change the cool temp"
if (deltaTemp < 0) {
lowCoolLevel()
} else if (deltaTemp > 0) {
raiseCoolLevel()
}
}
}
def raiseHeatLevel(){
def mode = device.currentValue("thermostatMode")
if (mode == "off") {
log.warn "raiseHeatLevel(): this mode: $mode does not allow raiseHeatLevel"
} else {
def locationScale = getTemperatureScale()
def maxTemp
def minTemp
if (locationScale == "C") {
maxTemp = 44 // Max Temp in C
minTemp = 7 // Min Temp in C
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
} else {
maxTemp = 86 // Max temp in F
minTemp = 30 // Max temp in F
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
}
def currentLevel = device.currentValue("heatingSetpoint")
int nextLevel = currentLevel.toInteger() + 1
if( nextLevel > maxTemp){
nextLevel = maxTemp
}
log.debug "Setting heat set point up to: ${nextLevel}"
setHeatingSetpoint(nextLevel)
}
}
def lowHeatLevel(){
if (mode == "off") {
log.warn "lowHeatLevel(): this mode: $mode does not allow lowHeatLevel"
} else {
def locationScale = getTemperatureScale()
def maxTemp
def minTemp
if (locationScale == "C") {
maxTemp = 44 // Max Temp in C
minTemp = 7 // Min Temp in C
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
} else {
maxTemp = 86 // Max temp in F
minTemp = 30 // Max temp in F
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
}
def currentLevel = device.currentValue("heatingSetpoint")
int nextLevel = currentLevel.toInteger() - 1
if( nextLevel < minTemp){
nextLevel = minTemp
}
log.debug "Setting heat set point down to: ${nextLevel}"
setHeatingSetpoint(nextLevel)
}
}
def raiseCoolLevel(){
if (mode == "off") {
log.warn "raiseCoolLevel(): this mode: $mode does not allow raiseCoolLevel"
} else {
def locationScale = getTemperatureScale()
def maxTemp
def minTemp
if (locationScale == "C") {
maxTemp = 44 // Max Temp in C
minTemp = 7 // Min Temp in C
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
} else {
maxTemp = 86 // Max temp in F
minTemp = 30 // Max temp in F
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
}
def currentLevel = device.currentValue("coolingSetpoint")
int nextLevel = currentLevel.toInteger() + 1
if( nextLevel > maxTemp){
nextLevel = maxTemp
}
log.debug "Setting cool set point up to: ${nextLevel}"
setCoolingSetpoint(nextLevel)
}
}
def lowCoolLevel(){
if (mode == "off") {
log.warn "lowCoolLevel(): this mode: $mode does not allow lowCoolLevel"
} else {
def locationScale = getTemperatureScale()
def maxTemp
def minTemp
if (locationScale == "C") {
maxTemp = 44 // Max Temp in C
minTemp = 7 // Min Temp in C
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
} else {
maxTemp = 86 // Max temp in F
minTemp = 30 // Max temp in F
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
}
def currentLevel = device.currentValue("coolingSetpoint")
int nextLevel = currentLevel.toInteger() - 1
if( nextLevel < minTemp){
nextLevel = minTemp
}
log.debug "Setting cool set point down to: ${nextLevel}"
setCoolingSetpoint(nextLevel)
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parse description $description"
def map = [:]
if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.debug "Desc Map: $descMap"
if (descMap.cluster == "0201" && descMap.attrId == "0000") {
log.debug "TEMP"
map.name = "temperature"
map.value = getTemperature(descMap.value)
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
log.debug "COOLING SETPOINT"
map.name = "coolingSetpoint"
map.value = getTemperature(descMap.value)
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
log.debug "HEATING SETPOINT"
map.name = "heatingSetpoint"
map.value = getTemperature(descMap.value)
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
log.debug "MODE"
map.name = "thermostatMode"
map.value = getModeMap()[descMap.value]
} else if (descMap.cluster == "0202" && descMap.attrId == "0000") {
log.debug "FAN MODE"
map.name = "thermostatFanMode"
map.value = getFanModeMap()[descMap.value]
} else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
log.debug "BATTERY"
map.name = "battery"
map.value = getBatteryLevel(descMap.value)
} else if (descMap.cluster == "0201" && descMap.attrId == "001e") {
log.debug "RUN MODE"
map.name = "thermostatRunMode"
//map.value = getRunModeMap()[descMap.value]
map.value = getThermostatRunMode(descMap.value)
} else if (descMap.cluster == "0201" && descMap.attrId == "0023") {
log.debug "HOLD MODE"
map.name = "thermostatHoldMode"
map.value = getHoldModeMap()[descMap.value]
} else if (descMap.cluster == "0000" && descMap.attrId == "0007") {
log.debug "Power Source"
map.name = "powerSource"
map.value = getPowerSource()[descMap.value]
}
}
def result = null
if (map) {
result = createEvent(map)
}
log.debug "Parse returned $map"
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def getModeMap() { [
"00":"off",
//"01":"auto",
"03":"cool",
"04":"heat",
"05":"emergencyHeat"
]}
def getHoldModeMap() { [
"00":"holdOff",
"01":"holdOn",
]}
def getPowerSource() { [
"01":"24VAC",
"03":"Battery",
]}
def getFanModeMap() { [
"04":"fanOn",
"05":"fanAuto"
]}
def getThermostatRunMode(value) {
if (value != null) {
def RunModeValue = Integer.parseInt(value, 16)
log.debug "Thermostat RunMode: ${RunModeValue} "
}
}
def getTemperature(value) {
if (value != null) {
def celsius = Integer.parseInt(value, 16) / 100
if (getTemperatureScale() == "C") {
return celsius
} else {
return Math.round(celsiusToFahrenheit(celsius))
}
}
}
def setHeatingSetpoint(degrees) {
if (degrees != null) {
def temperatureScale = getTemperatureScale()
def degreesInteger = Math.round(degrees)
log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "heatingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}"
}
}
def setCoolingSetpoint(degrees) {
if (degrees != null) {
def degreesInteger = Math.round(degrees)
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "coolingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
}
}
def modes() {
["off", "cool", "heat", "emergencyHeat"]
}
def setThermostatMode() {
log.debug "switching thermostatMode"
def currentMode = device.currentState("thermostatMode")?.value
def modeOrder = modes()
log.debug "modeOrder: ${modeOrder}"
def index = modeOrder.indexOf(currentMode)
log.debug "index: ${index}"
def next = index >= 0 && index < modeOrder.size() - 1 ? modeOrder[index + 1] : modeOrder[0]
log.debug "switching mode from $currentMode to $next"
"$next"()
}
def setThermostatFanMode() {
log.debug "Switching fan mode"
def currentFanMode = device.currentState("thermostatFanMode")?.value
log.debug "switching fan from current mode: $currentFanMode"
def returnCommand
switch (currentFanMode) {
case "fanAuto":
returnCommand = fanOn()
break
case "fanOn":
returnCommand = fanAuto()
break
}
if(!currentFanMode) { returnCommand = fanAuto() }
returnCommand
}
def setThermostatHoldMode() {
log.debug "Switching Hold mode"
def currentHoldMode = device.currentState("thermostatHoldMode")?.value
log.debug "switching thermostat from current mode: $currentHoldMode"
def returnCommand
switch (currentHoldMode) {
case "holdOff":
returnCommand = holdOn()
break
case "holdOn":
returnCommand = holdOff()
break
}
if(!currentHoldMode) { returnCommand = holdOff() }
returnCommand
}
def setThermostatMode(String value) {
log.debug "setThermostatMode({$value})"
"$value"()
}
def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode({$value})"
"$value"()
}
def setThermostatHoldMode(String value) {
log.debug "setThermostatHoldMode({$value})"
"$value"()
}
def off() {
log.debug "off"
sendEvent("name":"thermostatMode", "value":"off")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {00}"
}
def cool() {
log.debug "cool"
sendEvent("name":"thermostatMode", "value":"cool")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {03}"
}
def heat() {
log.debug "heat"
sendEvent("name":"thermostatMode", "value":"heat")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {04}"
}
def emergencyHeat() {
log.debug "emergencyHeat"
sendEvent("name":"thermostatMode", "value":"emergencyHeat")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {05}"
}
def on() {
fanOn()
}
def fanOn() {
log.debug "fanOn"
sendEvent("name":"thermostatFanMode", "value":"fanOn")
"st wattr 0x${device.deviceNetworkId} 1 0x202 0 0x30 {04}"
}
def auto() {
fanAuto()
}
def fanAuto() {
log.debug "fanAuto"
sendEvent("name":"thermostatFanMode", "value":"fanAuto")
"st wattr 0x${device.deviceNetworkId} 1 0x202 0 0x30 {05}"
}
def holdOn() {
log.debug "Set Hold On for thermostat"
sendEvent("name":"thermostatHoldMode", "value":"holdOn")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x23 0x30 {01}"
}
def holdOff() {
log.debug "Set Hold Off for thermostat"
sendEvent("name":"thermostatHoldMode", "value":"holdOff")
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x23 0x30 {00}"
}
// Commment out below if no C-wire since it will kill the batteries.
def poll() {
// log.debug "Executing 'poll'"
refresh()
}
def configure() {
log.debug "binding to Thermostat and Fan Control cluster"
[
"zdo bind 0x${device.deviceNetworkId} 1 1 0x000 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x201 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x202 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 3600 86400 {01}", "delay 100", //battery report request
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}
def refresh()
{
log.debug "refresh called"
[
"st rattr 0x${device.deviceNetworkId} 1 0x000 0x07", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x11", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x12", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x1C", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x1E", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x23", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x001 0x20", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x202 0"
] + configure()
}
private getBatteryLevel(rawValue) {
def intValue = Integer.parseInt(rawValue,16)
def min = 2.1
def max = 3.0
def vBatt = intValue / 10
return ((vBatt - min) / (max - min) * 100) as int
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}

View File

@@ -54,6 +54,12 @@ def parse(String description) {
def event = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (event) { 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) {} if (event.name=="level" && event.value==0) {}
else { else {
sendEvent(event) sendEvent(event)
@@ -80,7 +86,15 @@ def setLevel(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
return zigbee.onOffRefresh()
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() { def refresh() {
@@ -89,8 +103,7 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min // Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -85,6 +85,12 @@ def parse(String description) {
def event = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (event) { if (event) {
log.debug 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) {} if (event.name=="level" && event.value==0) {}
else { else {
if (event.name=="colorTemperature") { if (event.name=="colorTemperature") {
@@ -99,7 +105,7 @@ def parse(String description) {
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) { if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100) def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed") sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
} }
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
@@ -124,7 +130,15 @@ def off() {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
return zigbee.onOffRefresh()
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() { def refresh() {
@@ -133,10 +147,9 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min // Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity 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)
zigbee.onOffConfig(0, 300) + 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)
} }
def setColorTemperature(value) { def setColorTemperature(value) {

View File

@@ -74,6 +74,12 @@ def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def event = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (event) { 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) {} if (event.name=="level" && event.value==0) {}
else { else {
if (event.name=="colorTemperature") { if (event.name=="colorTemperature") {
@@ -104,7 +110,15 @@ def setLevel(value) {
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Device
* */ * */
def ping() { def ping() {
return zigbee.onOffRefresh()
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() { def refresh() {
@@ -113,10 +127,9 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min // Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
} }
def setColorTemperature(value) { def setColorTemperature(value) {

View File

@@ -65,16 +65,7 @@ void updateSwitch() {
private void updateAll(devices) { private void updateAll(devices) {
def command = request.JSON?.command def command = request.JSON?.command
if (command) { if (command) {
switch(command) { devices."$command"()
case "on":
devices.on()
break
case "off":
devices.off()
break
default:
httpError(403, "Access denied. This command is not supported by current capability.")
}
} }
} }
@@ -86,16 +77,7 @@ private void update(devices) {
if (!device) { if (!device) {
httpError(404, "Device not found") httpError(404, "Device not found")
} else { } else {
switch(command) { device."$command"()
case "on":
device.on()
break
case "off":
device.off()
break
default:
httpError(403, "Access denied. This command is not supported by current capability.")
}
} }
} }
} }

View File

@@ -58,7 +58,7 @@ def authPage() {
if (canInstallLabs()) { if (canInstallLabs()) {
def redirectUrl = getBuildRedirectUrl() def redirectUrl = getBuildRedirectUrl()
// log.debug "Redirect url = ${redirectUrl}" log.debug "Redirect url = ${redirectUrl}"
if (state.authToken) { if (state.authToken) {
description = "Tap 'Next' to proceed" description = "Tap 'Next' to proceed"
@@ -113,13 +113,13 @@ def oauthInitUrl() {
scope: "read_station" scope: "read_station"
] ]
// log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}" log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
redirect (location: getVendorAuthPath() + toQueryString(oauthParams)) redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
} }
def callback() { def callback() {
// log.debug "callback()>> params: $params, params.code ${params.code}" log.debug "callback()>> params: $params, params.code ${params.code}"
def code = params.code def code = params.code
def oauthState = params.state def oauthState = params.state
@@ -135,7 +135,7 @@ def callback() {
scope: "read_station" scope: "read_station"
] ]
// log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}" log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
def tokenUrl = getVendorTokenPath() def tokenUrl = getVendorTokenPath()
def params = [ def params = [
@@ -144,7 +144,7 @@ def callback() {
body: tokenParams body: tokenParams
] ]
// log.debug "PARAMS: ${params}" log.debug "PARAMS: ${params}"
httpPost(params) { resp -> httpPost(params) { resp ->
@@ -156,7 +156,7 @@ def callback() {
state.refreshToken = data.refresh_token state.refreshToken = data.refresh_token
state.authToken = data.access_token state.authToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000) state.tokenExpires = now() + (data.expires_in * 1000)
// log.debug "swapped token: $resp.data" log.debug "swapped token: $resp.data"
} }
} }
@@ -292,7 +292,7 @@ def refreshToken() {
response.data.each {key, value -> response.data.each {key, value ->
def data = slurper.parseText(key); def data = slurper.parseText(key);
// log.debug "Data: $data" log.debug "Data: $data"
state.refreshToken = data.refresh_token state.refreshToken = data.refresh_token
state.accessToken = data.access_token state.accessToken = data.access_token

View File

@@ -26,9 +26,9 @@ preferences {
} }
section("Temperature monitor?") { section("Temperature monitor?") {
input "temp", "capability.temperatureMeasurement", title: "Temperature Sensor", required: false input "temp", "capability.temperatureMeasurement", title: "Temp Sensor", required: false
input "maxTemp", "number", title: "Max Temperature (°${location.temperatureScale})", required: false input "maxTemp", "number", title: "Max Temp?", required: false
input "minTemp", "number", title: "Min Temperature (°${location.temperatureScale})", required: false input "minTemp", "number", title: "Min Temp?", required: false
} }
section("When which people are away?") { section("When which people are away?") {

View File

@@ -14,7 +14,7 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
definition( definition(
name: "Smart Home Ventilation", name: "Smart Home Ventilation",
namespace: "MichaelStruck", namespace: "MichaelStruck",
@@ -164,7 +164,7 @@ def installed() {
def updated() { def updated() {
unschedule() unschedule()
turnOffSwitch() //Turn off all switches if the schedules are changed while in mid-schedule turnOffSwitch() //Turn off all switches if the schedules are changed while in mid-schedule
unsubscribe() unsubscribe
log.debug "Updated with settings: ${settings}" log.debug "Updated with settings: ${settings}"
init() init()
} }
@@ -174,12 +174,12 @@ def init() {
schedule (midnightTime, midNight) schedule (midnightTime, midNight)
subscribe(location, "mode", locationHandler) subscribe(location, "mode", locationHandler)
startProcess() startProcess()
} }
// Common methods // Common methods
def startProcess () { def startProcess () {
createDayArray() createDayArray()
state.dayCount=state.data.size() state.dayCount=state.data.size()
if (state.dayCount){ if (state.dayCount){
state.counter = 0 state.counter = 0
@@ -190,7 +190,7 @@ def startProcess () {
def startDay() { def startDay() {
def start = convertEpoch(state.data[state.counter].start) def start = convertEpoch(state.data[state.counter].start)
def stop = convertEpoch(state.data[state.counter].stop) def stop = convertEpoch(state.data[state.counter].stop)
runOnce(start, turnOnSwitch, [overwrite: true]) runOnce(start, turnOnSwitch, [overwrite: true])
runOnce(stop, incDay, [overwrite: true]) runOnce(stop, incDay, [overwrite: true])
} }
@@ -218,7 +218,7 @@ def locationHandler(evt) {
} }
if (!result) { if (!result) {
startProcess() startProcess()
} }
} }
def midNight(){ def midNight(){
@@ -238,7 +238,7 @@ def turnOffSwitch() {
} }
log.debug "Home ventilation switches are off." log.debug "Home ventilation switches are off."
} }
def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) { def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
def title = "" def title = ""
def dayListClean = "On " def dayListClean = "On "
@@ -252,7 +252,7 @@ def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
dayListClean = "${dayListClean}, " dayListClean = "${dayListClean}, "
} }
} }
} }
else { else {
dayListClean = "Every day" dayListClean = "Every day"
} }
@@ -272,7 +272,7 @@ def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
modeListClean = "${modeListClean} ${modePrefix}" modeListClean = "${modeListClean} ${modePrefix}"
} }
} }
} }
else { else {
modeListClean = "${modeListClean}all modes" modeListClean = "${modeListClean}all modes"
} }
@@ -283,16 +283,16 @@ def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
title += "\nSchedule 2: ${humanReadableTime(on2)} to ${humanReadableTime(off2)}" title += "\nSchedule 2: ${humanReadableTime(on2)} to ${humanReadableTime(off2)}"
} }
if (on3 && off3) { if (on3 && off3) {
title += "\nSchedule 3: ${humanReadableTime(on3)} to ${humanReadableTime(off3)}" title += "\nSchedule 3: ${humanReadableTime(on3)} to ${humanReadableTime(off3)}"
} }
if (on4 && off4) { if (on4 && off4) {
title += "\nSchedule 4: ${humanReadableTime(on4)} to ${humanReadableTime(off4)}" title += "\nSchedule 4: ${humanReadableTime(on4)} to ${humanReadableTime(off4)}"
} }
if (on1 || on2 || on3 || on4) { if (on1 || on2 || on3 || on4) {
title += "\n$modeListClean" title += "\n$modeListClean"
title += "\n$dayListClean" title += "\n$dayListClean"
} }
if (!on1 && !on2 && !on3 && !on4) { if (!on1 && !on2 && !on3 && !on4) {
title="Click to configure scenario" title="Click to configure scenario"
} }
@@ -374,7 +374,7 @@ def createDayArray() {
timeOk(timeOnD1, timeOffD1) timeOk(timeOnD1, timeOffD1)
timeOk(timeOnD2, timeOffD2) timeOk(timeOnD2, timeOffD2)
timeOk(timeOnD3, timeOffD3) timeOk(timeOnD3, timeOffD3)
timeOk(timeOnD4, timeOffD4) timeOk(timeOnD4, timeOffD4)
} }
} }
state.data.sort{it.start} state.data.sort{it.start}
@@ -384,7 +384,7 @@ def createDayArray() {
private def textAppName() { private def textAppName() {
def text = "Smart Home Ventilation" def text = "Smart Home Ventilation"
} }
private def textVersion() { private def textVersion() {
def text = "Version 2.1.2 (05/31/2015)" def text = "Version 2.1.2 (05/31/2015)"
@@ -416,4 +416,4 @@ private def textHelp() {
"that each scenario does not overlap and run in separate modes (i.e. Home, Out of town, etc). Also note that you should " + "that each scenario does not overlap and run in separate modes (i.e. Home, Out of town, etc). Also note that you should " +
"avoid scheduling the ventilation fan at exactly midnight; the app resets itself at that time. It is suggested to start any new schedule " + "avoid scheduling the ventilation fan at exactly midnight; the app resets itself at that time. It is suggested to start any new schedule " +
"at 12:15 am or later." "at 12:15 am or later."
} }

View File

@@ -17,7 +17,7 @@ definition(
name: "Monitor on Sense", name: "Monitor on Sense",
namespace: "resteele", namespace: "resteele",
author: "Rachel Steele", author: "Rachel Steele",
description: "Turn on switch when vibration is sensed", description: "Turn on Monitor when vibration is sensed",
category: "My Apps", category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
@@ -25,10 +25,10 @@ definition(
preferences { preferences {
section("When vibration is sensed...") { section("When the keyboard is used...") {
input "accelerationSensor", "capability.accelerationSensor", title: "Which Sensor?" input "accelerationSensor", "capability.accelerationSensor", title: "Which Sensor?"
} }
section("Turn on switch...") { section("Turn on/off a light...") {
input "switch1", "capability.switch" input "switch1", "capability.switch"
} }
} }
@@ -47,3 +47,5 @@ def updated() {
def accelerationActiveHandler(evt) { def accelerationActiveHandler(evt) {
switch1.on() switch1.on()
} }

View File

@@ -114,16 +114,13 @@ def beaconHandler(evt) {
if (allOk) { if (allOk) {
def data = new groovy.json.JsonSlurper().parseText(evt.data) def data = new groovy.json.JsonSlurper().parseText(evt.data)
// removed logging of device names. can be added back for debugging log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
//log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
def beaconName = getBeaconName(evt) def beaconName = getBeaconName(evt)
// removed logging of device names. can be added back for debugging log.debug "<beacon-control> beaconName: $beaconName"
//log.debug "<beacon-control> beaconName: $beaconName"
def phoneName = getPhoneName(data) def phoneName = getPhoneName(data)
// removed logging of device names. can be added back for debugging log.debug "<beacon-control> phoneName: $phoneName"
//log.debug "<beacon-control> phoneName: $phoneName"
if (phoneName != null) { if (phoneName != null) {
def action = data.presence == "1" ? "arrived" : "left" def action = data.presence == "1" ? "arrived" : "left"
def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName" def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName"

View File

@@ -49,15 +49,13 @@ preferences {
def installed() { def installed() {
log.debug "Installed with settings: ${settings}" log.debug "Installed with settings: ${settings}"
// commented out log statement because presence sensor label could contain user's name log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
//log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
subscribe(people, "presence", presence) subscribe(people, "presence", presence)
} }
def updated() { def updated() {
log.debug "Updated with settings: ${settings}" log.debug "Updated with settings: ${settings}"
// commented out log statement because presence sensor label could contain user's name log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
//log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
unsubscribe() unsubscribe()
subscribe(people, "presence", presence) subscribe(people, "presence", presence)
} }

View File

@@ -20,8 +20,6 @@
* JLH - 02-15-2014 - Fuller use of ecobee API * JLH - 02-15-2014 - Fuller use of ecobee API
* 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines * 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines
*/ */
include 'asynchttp_v1'
definition( definition(
name: "Ecobee (Connect)", name: "Ecobee (Connect)",
namespace: "smartthings", namespace: "smartthings",
@@ -246,7 +244,9 @@ def getEcobeeThermostats() {
uri: apiEndpoint, uri: apiEndpoint,
path: "/1/thermostat", path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"], headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
query: [json: toJson(bodyParams)] // TODO - the query string below is not consistent with the Ecobee docs:
// https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
query: [format: 'json', body: toJson(bodyParams)]
] ]
def stats = [:] def stats = [:]
@@ -265,8 +265,9 @@ def getEcobeeThermostats() {
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) { if (e.response.data.status.code == 14) {
atomicState.action = "getEcobeeThermostats"
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
refreshAuthToken([async: false, nextAction: "getEcobeeThermostats"]) refreshAuthToken()
} }
} }
atomicState.thermostats = stats atomicState.thermostats = stats
@@ -357,22 +358,16 @@ def initialize() {
atomicState.timeSendPush = null atomicState.timeSendPush = null
atomicState.reAttempt = 0 atomicState.reAttempt = 0
initialPoll() //first time polling data data from thermostat pollHandler() //first time polling data data from thermostat
//automatically update devices status every 5 mins //automatically update devices status every 5 mins
runEvery5Minutes("poll") runEvery5Minutes("poll")
} }
/** def pollHandler() {
* Polls the child devices (synchronously). log.debug "pollHandler()"
* This is used during app install/update, and is synchronous pollChildren(null) // Hit the ecobee API for update on all thermostats
* to maintain current behavior that will cause install/update to fail
* if polling fails.
*/
def initialPoll() {
log.debug "initialPoll()"
pollChildrenSync() // Hit the ecobee API for update on all thermostats
atomicState.thermostats.each {stat -> atomicState.thermostats.each {stat ->
def dni = stat.key def dni = stat.key
@@ -385,101 +380,10 @@ def initialPoll() {
} }
} }
/** def pollChildren(child = null) {
* Polls Ecobee (asynchronously) for updated device state data. def thermostatIdsString = getChildDeviceIdsString()
* Called from within this Connect SmartApp as well as the child
* devices.
*/
def poll() {
log.debug "polling asynchronously"
asynchttp_v1.get('asyncPollResponseHandler', getPollParams())
}
/**
* Makes a (synchronous) request to the Ecobee API to get the data for the thermostats.
* This request is made synchronously here because it is called as part of the
* install/updated lifecycle, and changing it to asynchronous during the install/update
* lifecycle may change the behavior if there is an error in polling.
*
* If further analysis shows that polling can be done asynchronously during
* install/update without any adverse consequences, this should then be made
* asynchronous just as the scheduled polling is.
*/
def pollChildrenSync() {
log.debug "polling children: $thermostatIdsString" log.debug "polling children: $thermostatIdsString"
def params = getPollParams()
params.query << ["Content-Type": "application/json"]
def result = false
log.debug "making synchronous poll request"
try{
httpGet(params) { resp ->
if(resp.status == 200) {
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
updateSensorData()
storeThermostatData(resp.data.thermostatList)
result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
log.debug "Refreshing your auth_token!"
refreshAuthToken([async: false, nextAction: "pollChildrenSync"])
}
}
return result
}
/**
* Response handler for asynchronous request to get thermostat data.
* Given a successful response, updates the sensor data, stores the thermostat
* data, and generates child device events.
*
* If the access token has expired, will issue a request to refresh the token
* (and pending successful token refresh, the poll request will be made again).
*/
def asyncPollResponseHandler(response, data) {
log.trace "async poll response handler"
if (!response.hasError()) {
if (response.status == 200) {
def json
try {
json = response.getJson()
} catch (e) {
log.error ("error parsing JSON", e)
}
if (json) {
atomicState.remoteSensors = json.thermostatList.remoteSensors
updateSensorData()
storeThermostatData(json.thermostatList)
generateChildThermostatEvent()
}
} else {
log.warn "Response returned non-200 response. Status: ${response.status}, data: ${response.getData()}"
}
} else {
log.trace "Exception polling children: ${response.getErrorMessage()}"
def errorJson
try {
errorJson = response.getErrorJson()
} catch (e) {
log.error("Unable to parse error json response", e)
}
if (errorJson?.status?.code == 14) {
log.debug "Refreshing your auth_token!"
refreshAuthToken([async: true, nextAction: "poll"])
} else {
log.warn "Error polling children that is not due to an expired token. Response: ${response.getErrorData()}"
}
}
}
private getPollParams() {
def thermostatIdsString = getChildDeviceIdsString()
def requestBody = [ def requestBody = [
selection: [ selection: [
selectionType: "thermostats", selectionType: "thermostats",
@@ -490,32 +394,66 @@ private getPollParams() {
includeSensors: true includeSensors: true
] ]
] ]
return [
def result = false
def pollParams = [
uri: apiEndpoint, uri: apiEndpoint,
path: "/1/thermostat", path: "/1/thermostat",
headers: ["Authorization": "Bearer ${atomicState.authToken}"], headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
query: [json: toJson(requestBody)] // TODO - the query string below is not consistent with the Ecobee docs:
// https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
query: [format: 'json', body: toJson(requestBody)]
] ]
try{
httpGet(pollParams) { resp ->
if(resp.status == 200) {
log.debug "poll results returned resp.data ${resp.data}"
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
updateSensorData()
storeThermostatData(resp.data.thermostatList)
result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
}
return result
} }
/** // Poll Child is invoked from the Child Device itself as part of the Poll Capability
* Calls each child thermostat device to generate an event with the thermostat def pollChild() {
* data. def devices = getChildDevices()
*/
def generateChildThermostatEvent() { if (pollChildren()) {
log.trace("generateChildThermostatEvent") devices.each { child ->
getChildDevices().each { child -> if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")) {
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){ if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
if(atomicState.thermostats[child.device.deviceNetworkId] != null) { def tData = atomicState.thermostats[child.device.deviceNetworkId]
def tData = atomicState.thermostats[child.device.deviceNetworkId] log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
log.debug "calling child.generateEvent($tData.data)" child.generateEvent(tData.data) //parse received message from parent
child.generateEvent(tData.data) //parse received message from parent } else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) { log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}" return null
return null }
} }
} }
} } else {
log.info "ERROR: pollChildren()"
return null
}
}
void poll() {
pollChild()
} }
def availableModes(child) { def availableModes(child) {
@@ -615,104 +553,47 @@ def toQueryString(Map m) {
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&") return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
} }
/** private refreshAuthToken() {
* Uses the refresh token to get a new access token, then executes the nextAction. log.debug "refreshing auth token"
* @param options - a map of options. valid options are async: true/false, which
* specifies if the refresh token request will be done asynchronously or not (default is false)
* nextAction: "nameOfMethod" specifies what method to execute after
* the token is refreshed (not required).
* (note: using a map as the parameter because we need to call it from a schedueled
* execution and we can only pass a data map to scheduled executions)
*/
private void refreshAuthToken(options) {
if(!atomicState.refreshToken) {
log.warn "Cannot not refresh OAuth token since there is no refreshToken stored"
} else {
def refreshParams = [
uri : apiEndpoint,
path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
]
if (options.async) {
refreshAuthTokenAsync(refreshParams, options.nextAction)
} else {
refreshAuthTokenSync(refreshParams, options.nextAction)
}
}
}
private void refreshAuthTokenSync(params, nextAction = null) { if(!atomicState.refreshToken) {
try { log.warn "Can not refresh OAuth token since there is no refreshToken stored"
httpPost(refreshParams) { resp -> } else {
if(resp.status == 200) { def refreshParams = [
log.debug "Token refreshed...calling saved RestAction now!" method: 'POST',
debugEvent("Token refreshed ... calling saved RestAction now!") uri : apiEndpoint,
saveTokenAndResumeAction(resp.data, nextAction) path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
]
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
//changed to httpPost
try {
def jsonMap
httpPost(refreshParams) { resp ->
if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!"
debugEvent("Token refreshed ... calling saved RestAction now!")
saveTokenAndResumeAction(resp.data)
}
} }
} } catch (groovyx.net.http.HttpResponseException e) {
} catch (groovyx.net.http.HttpResponseException e) { log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}" def reAttemptPeriod = 300 // in sec
reauthTokenErrorHandler(e.statusCode) if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc.
} runIn(reAttemptPeriod, "refreshAuthToken")
} } else if (e.statusCode == 401) { // unauthorized
atomicState.reAttempt = atomicState.reAttempt + 1
private void refreshAuthTokenAsync(refreshParams, nextAction = null) { log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
log.debug "making asynchronous refresh request" if (atomicState.reAttempt <= 3) {
asynchttp_v1.post('refreshTokenResponseHandler', refreshParams, [nextAction: nextAction]) runIn(reAttemptPeriod, "refreshAuthToken")
} } else {
sendPushAndFeeds(notificationMessage)
/** atomicState.reAttempt = 0
* The response handler for the request to refresh the authorization handler. }
* Stores the new authorization token and refresh token, and executes any action }
* (method) that failed due to the authorization token expiring. }
*/ }
private void refreshTokenResponseHandler(response, data) {
if (!response.hasError()) {
if (response.status == 200) {
def json
try {
json = response.getJson()
} catch (e) {
log.error "error parsing json from response data: $response.data"
}
if (json) {
log.debug "asnyc refreshTokenHandler: Token refreshed...calling saved RestAction now!"
debugEvent("async Token refreshed ... calling saved RestAction now!")
saveTokenAndResumeAction(json, data.nextAction)
} else {
log.warn "successfully parsed json but result is empty or null"
}
} else {
log.debug "Non 200 response returned. Response code: ${response.code}, data: ${response.getData()}"
}
} else {
log.debug "async refreshTokenHandler: RESPONSE ERROR: ${response.getErrorJson()}"
reauthTokenErrorHandler(response.getErrorJson().code)
}
}
/**
* Retries refreshing the authorization token. Will attempt to get the refresh
* token later, in case there were errors retrieving it.
* Will retry a fixed number of times before sending a push notification to the
* user instructing them to reauthenticate
*/
private void reauthTokenErrorHandler(responseCode) {
def retryInterval = 300 // in seconds
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
// might get non-401 error from exceeding 20 second app limit, connectivity issues, etc.
if (responseCode != 401) {
runIn(retryInterval, "refreshAuthToken", [async: true])
} else if (responseCode == 401) { // unauthorized
atomicState.reAttempt = atomicState.reAttempt + 1
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
if (atomicState.reAttempt <= 3) {
runIn(retryInterval, "refreshAuthToken", [async: true])
} else {
sendPushAndFeeds(notificationMessage)
atomicState.reAttempt = 0
}
}
} }
/** /**
@@ -722,20 +603,20 @@ private void reauthTokenErrorHandler(responseCode) {
* *
* @param json - an object representing the parsed JSON response from Ecobee * @param json - an object representing the parsed JSON response from Ecobee
*/ */
private void saveTokenAndResumeAction(json, String nextAction) { private void saveTokenAndResumeAction(json) {
def debugMessage = "token response, scope: ${json?.scope}, expires_in: ${json?.expires_in}, token_type: ${json?.token_type}" log.debug "token response json: $json"
log.debug "debugMessage"
if (json) { if (json) {
debugEvent(debugMessage) debugEvent("Response = $json")
atomicState.refreshToken = json?.refresh_token atomicState.refreshToken = json?.refresh_token
atomicState.authToken = json?.access_token atomicState.authToken = json?.access_token
if (nextAction) { if (atomicState.action) {
log.debug "got refresh token, will execute next action (passed in!): $nextAction" log.debug "got refresh token, executing next action: ${atomicState.action}"
"$nextAction"() "${atomicState.action}"()
} }
} else { } else {
log.warn "did not get response body from refresh token response" log.warn "did not get response body from refresh token response"
} }
atomicState.action = ""
} }
/** /**
@@ -875,6 +756,7 @@ private boolean sendCommandToEcobee(Map bodyParams) {
try{ try{
httpPost(cmdParams) { resp -> httpPost(cmdParams) { resp ->
if(resp.status == 200) { if(resp.status == 200) {
log.debug "updated ${resp.data}"
def returnStatus = resp.data.status.code def returnStatus = resp.data.status.code
if (returnStatus == 0) { if (returnStatus == 0) {
log.debug "Successful call to ecobee API." log.debug "Successful call to ecobee API."
@@ -889,10 +771,11 @@ private boolean sendCommandToEcobee(Map bodyParams) {
log.trace "Exception Sending Json: " + e.response.data.status log.trace "Exception Sending Json: " + e.response.data.status
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}") debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
if (e.response.data.status.code == 14) { if (e.response.data.status.code == 14) {
// TODO - figure out why we're setting the next action to be poll // TODO - figure out why we're setting the next action to be pollChildren
// after refreshing auth token. Is it to keep UI in sync, or just copy/paste error? // after refreshing auth token. Is it to keep UI in sync, or just copy/paste error?
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!" log.debug "Refreshing your auth_token!"
refreshAuthToken([async: true, nextAction: "poll"]) refreshAuthToken()
} else { } else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.") debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
log.error "Authentication error, invalid authentication method, lack of credentials, etc." log.error "Authentication error, invalid authentication method, lack of credentials, etc."

View File

@@ -64,12 +64,10 @@ def meterHandler(evt) {
def lastValue = atomicState.lastValue as double def lastValue = atomicState.lastValue as double
atomicState.lastValue = meterValue atomicState.lastValue = meterValue
def dUnit = evt.unit ?: "Watts"
def aboveThresholdValue = aboveThreshold as int def aboveThresholdValue = aboveThreshold as int
if (meterValue > aboveThresholdValue) { if (meterValue > aboveThresholdValue) {
if (lastValue < aboveThresholdValue) { // only send notifications when crossing the threshold if (lastValue < aboveThresholdValue) { // only send notifications when crossing the threshold
def msg = "${meter} reported ${evt.value} ${dUnit} which is above your threshold of ${aboveThreshold}." def msg = "${meter} reported ${evt.value} ${evt.unit} which is above your threshold of ${aboveThreshold}."
sendMessage(msg) sendMessage(msg)
} else { } else {
// log.debug "not sending notification for ${evt.description} because the threshold (${aboveThreshold}) has already been crossed" // log.debug "not sending notification for ${evt.description} because the threshold (${aboveThreshold}) has already been crossed"
@@ -80,7 +78,7 @@ def meterHandler(evt) {
def belowThresholdValue = belowThreshold as int def belowThresholdValue = belowThreshold as int
if (meterValue < belowThresholdValue) { if (meterValue < belowThresholdValue) {
if (lastValue > belowThresholdValue) { // only send notifications when crossing the threshold if (lastValue > belowThresholdValue) { // only send notifications when crossing the threshold
def msg = "${meter} reported ${evt.value} ${dUnit} which is below your threshold of ${belowThreshold}." def msg = "${meter} reported ${evt.value} ${evt.unit} which is below your threshold of ${belowThreshold}."
sendMessage(msg) sendMessage(msg)
} else { } else {
// log.debug "not sending notification for ${evt.description} because the threshold (${belowThreshold}) has already been crossed" // log.debug "not sending notification for ${evt.description} because the threshold (${belowThreshold}) has already been crossed"

View File

@@ -54,10 +54,10 @@ def waterWetHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "wet" } > 1 def alreadySentSms = recentEvents.count { it.value && it.value == "wet" } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent within the last $deltaSeconds seconds" log.debug "SMS already sent to $phone within the last $deltaSeconds seconds"
} else { } else {
def msg = "${alarm.displayName} is wet!" def msg = "${alarm.displayName} is wet!"
log.debug "$alarm is wet, texting phone number" log.debug "$alarm is wet, texting $phone"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients) sendNotificationToContacts(msg, recipients)

View File

@@ -90,7 +90,7 @@ def takeAction(){
} }
def sendTextMessage() { def sendTextMessage() {
log.debug "$multisensor was open too long, texting phone" log.debug "$multisensor was open too long, texting $phone"
updateSmsHistory() updateSmsHistory()
def openMinutes = maxOpenTime * (state.smsHistory?.size() ?: 1) def openMinutes = maxOpenTime * (state.smsHistory?.size() ?: 1)

View File

@@ -761,7 +761,7 @@ String displayableTime(timeRemaining) {
return "${minutes}:00" return "${minutes}:00"
} }
def fraction = "0.${parts[1]}" as double def fraction = "0.${parts[1]}" as double
def seconds = "${60 * fraction as int}".padLeft(2, "0") def seconds = "${60 * fraction as int}".padRight(2, "0")
return "${minutes}:${seconds}" return "${minutes}:${seconds}"
} }
@@ -1101,4 +1101,4 @@ def hasStartLevel() {
def hasEndLevel() { def hasEndLevel() {
return (endLevel != null && endLevel != "") return (endLevel != null && endLevel != "")
} }

View File

@@ -47,13 +47,13 @@ preferences {
def installed() { def installed() {
log.debug "Installed with settings: ${settings}" log.debug "Installed with settings: ${settings}"
// log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}" log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
subscribe(people, "presence", presence) subscribe(people, "presence", presence)
} }
def updated() { def updated() {
log.debug "Updated with settings: ${settings}" log.debug "Updated with settings: ${settings}"
// log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}" log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
unsubscribe() unsubscribe()
subscribe(people, "presence", presence) subscribe(people, "presence", presence)
} }
@@ -71,10 +71,11 @@ def presence(evt)
def person = getPerson(evt) def person = getPerson(evt)
def recentNotPresent = person.statesSince("presence", t0).find{it.value == "not present"} def recentNotPresent = person.statesSince("presence", t0).find{it.value == "not present"}
if (recentNotPresent) { if (recentNotPresent) {
log.debug "skipping notification of arrival of Person because last departure was only ${now() - recentNotPresent.date.time} msec ago" log.debug "skipping notification of arrival of ${person.displayName} because last departure was only ${now() - recentNotPresent.date.time} msec ago"
} }
else { else {
def message = "${person.displayName} arrived at home, changing mode to '${newMode}'" def message = "${person.displayName} arrived at home, changing mode to '${newMode}'"
log.info message
send(message) send(message)
setLocationMode(newMode) setLocationMode(newMode)
} }
@@ -105,4 +106,6 @@ private send(msg) {
sendSms(phone, msg) sendSms(phone, msg)
} }
} }
log.debug msg
} }

View File

@@ -57,11 +57,12 @@ def scheduleCheck()
def message = message1 ?: "SmartThings - Habit Helper Reminder!" def message = message1 ?: "SmartThings - Habit Helper Reminder!"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
log.debug "Texting reminder to contacts:${recipients?.size()}" log.debug "Texting reminder: ($message) to contacts:${recipients?.size()}"
sendNotificationToContacts(message, recipients) sendNotificationToContacts(message, recipients)
} }
else { else {
log.debug "Texting reminder"
log.debug "Texting reminder: ($message) to $phone1"
sendSms(phone1, message) sendSms(phone1, message)
} }
} }

View File

@@ -170,7 +170,7 @@ def bulbDiscovery() {
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, options:newLights
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
} }
section { section {
@@ -724,13 +724,13 @@ private void updateBridgeStatus(childDevice) {
} }
/** /**
* Check if all Hue bridges have been heard from in the last 11 minutes, if not an Offline event will be sent * Check if all Hue bridges have been heard from in the last 16 minutes, if not an Offline event will be sent
* for the bridge and all connected lights. Also, set ID number on bridge if not done previously. * for the bridge. Also, set ID number on bridge if not done previously.
*/ */
private void checkBridgeStatus() { private void checkBridgeStatus() {
def bridges = getHueBridges() def bridges = getHueBridges()
// Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer) // Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 11) def time = now() - (1000 * 60 * 30)
bridges.each { bridges.each {
def d = getChildDevice(it.value.mac) def d = getChildDevice(it.value.mac)
if(d) { if(d) {
@@ -740,21 +740,16 @@ private void checkBridgeStatus() {
d.sendEvent(name: "idNumber", value: it.value.idNumber) d.sendEvent(name: "idNumber", value: it.value.idNumber)
} }
if (it.value.lastActivity < time) { // it.value.lastActivity != null && if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.key is Offline" log.warn "Bridge $it.key is Offline"
d.sendEvent(name: "status", value: "Offline") d.sendEvent(name: "status", value: "Offline")
// set all lights to offline since bridge is not reachable
state.bulbs?.each { state.bulbs?.each {it.value.online = false}
it.value.online = false } else {
}
getChildDevices().each {
it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false)
}
} else {
d.sendEvent(name: "status", value: "Online")//setOnline(false) d.sendEvent(name: "status", value: "Online")//setOnline(false)
} }
} }
} }
} }
def isValidSource(macAddress) { def isValidSource(macAddress) {
@@ -762,10 +757,6 @@ def isValidSource(macAddress) {
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
} }
def isInBulbDiscovery() {
return state.inBulbDiscovery
}
///////////////////////////////////// /////////////////////////////////////
//CHILD DEVICE METHODS //CHILD DEVICE METHODS
///////////////////////////////////// /////////////////////////////////////
@@ -960,7 +951,6 @@ private handlePoll(body) {
} else { } else {
state.bulbs[bulb.key]?.online = false state.bulbs[bulb.key]?.online = false
log.warn "$device is not reachable by Hue bridge" log.warn "$device is not reachable by Hue bridge"
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
} }
} }
} }

View File

@@ -53,14 +53,14 @@ def accelerationActiveHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1 def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent within the last $deltaSeconds seconds" log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
} else { } else {
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
log.debug "accelerationSensor has moved, texting contacts: ${recipients?.size()}" log.debug "$accelerationSensor has moved, texting contacts: ${recipients?.size()}"
sendNotificationToContacts("${accelerationSensor.label ?: accelerationSensor.name} moved", recipients) sendNotificationToContacts("${accelerationSensor.label ?: accelerationSensor.name} moved", recipients)
} }
else { else {
log.debug "accelerationSensor has moved, sending text message" log.debug "$accelerationSensor has moved, texting $phone1"
sendSms(phone1, "${accelerationSensor.label ?: accelerationSensor.name} moved") sendSms(phone1, "${accelerationSensor.label ?: accelerationSensor.name} moved")
} }
} }

View File

@@ -69,10 +69,10 @@ def temperatureHandler(evt) {
def alreadySentSms = recentEvents.count { it.doubleValue >= tooHot } > 1 def alreadySentSms = recentEvents.count { it.doubleValue >= tooHot } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent within the last $deltaMinutes minutes" log.debug "SMS already sent to $phone1 within the last $deltaMinutes minutes"
// TODO: Send "Temperature back to normal" SMS, turn switch off // TODO: Send "Temperature back to normal" SMS, turn switch off
} else { } else {
log.debug "Temperature rose above $tooHot: sending SMS and activating $mySwitch" log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch"
def tempScale = location.temperatureScale ?: "F" def tempScale = location.temperatureScale ?: "F"
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}") send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
switch1?.on() switch1?.on()

View File

@@ -50,9 +50,9 @@ def authPage() {
} }
def description = "Tap to enter LIFX credentials" def description = "Tap to enter LIFX credentials"
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below
// def redirectUrl = "${apiServerUrl}" // def redirectUrl = "${apiServerUrl}"
// log.debug "app id: ${app.id}" log.debug "app id: ${app.id}"
// log.debug "redirect url: ${redirectUrl}"s log.debug "redirect url: ${redirectUrl}"
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) { return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
section { section {
href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account") href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account")
@@ -372,7 +372,7 @@ def updateDevices() {
def childDevice = getChildDevice(device.id) def childDevice = getChildDevice(device.id)
selectors.add("${device.id}") selectors.add("${device.id}")
if (!childDevice) { if (!childDevice) {
// log.info("Adding device ${device.id}: ${device.product}") log.info("Adding device ${device.id}: ${device.product}")
def data = [ def data = [
label: device.label, label: device.label,
level: Math.round((device.brightness ?: 1) * 100), level: Math.round((device.brightness ?: 1) * 100),

View File

@@ -111,23 +111,16 @@ private sendMessage(evt) {
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients, options) sendNotificationToContacts(msg, recipients, options)
} else { } else {
if (pushAndPhone != 'No') {
log.debug 'sending push'
options.method = 'push'
sendNotification(msg, options)
}
if (phone) { if (phone) {
options.phone = phone options.phone = phone
if (pushAndPhone != 'No') { log.debug 'sending SMS'
log.debug 'Sending push and SMS' sendNotification(msg, options)
options.method = 'both'
} else {
log.debug 'Sending SMS'
options.method = 'phone'
}
} else if (pushAndPhone != 'No') {
log.debug 'Sending push'
options.method = 'push'
} else {
log.debug 'Sending nothing'
options.method = 'none'
} }
sendNotification(msg, options)
} }
if (frequency) { if (frequency) {
state[evt.deviceId] = now() state[evt.deviceId] = now()

View File

@@ -41,10 +41,10 @@ def updated() {
def presenceHandler(evt) { def presenceHandler(evt) {
if (evt.value == "present") { if (evt.value == "present") {
// log.debug "${presence.label ?: presence.name} has arrived at the ${location}" log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
sendPush("${presence.label ?: presence.name} has arrived at the ${location}") sendPush("${presence.label ?: presence.name} has arrived at the ${location}")
} else if (evt.value == "not present") { } else if (evt.value == "not present") {
// log.debug "${presence.label ?: presence.name} has left the ${location}" log.debug "${presence.label ?: presence.name} has left the ${location}"
sendPush("${presence.label ?: presence.name} has left the ${location}") sendPush("${presence.label ?: presence.name} has left the ${location}")
} }
} }

View File

@@ -47,7 +47,7 @@ def updated() {
def presenceHandler(evt) { def presenceHandler(evt) {
if (evt.value == "present") { if (evt.value == "present") {
// log.debug "${presence.label ?: presence.name} has arrived at the ${location}" log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts("${presence.label ?: presence.name} has arrived at the ${location}", recipients) sendNotificationToContacts("${presence.label ?: presence.name} has arrived at the ${location}", recipients)
@@ -56,7 +56,7 @@ def presenceHandler(evt) {
sendSms(phone1, "${presence.label ?: presence.name} has arrived at the ${location}") sendSms(phone1, "${presence.label ?: presence.name} has arrived at the ${location}")
} }
} else if (evt.value == "not present") { } else if (evt.value == "not present") {
// log.debug "${presence.label ?: presence.name} has left the ${location}" log.debug "${presence.label ?: presence.name} has left the ${location}"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts("${presence.label ?: presence.name} has left the ${location}", recipients) sendNotificationToContacts("${presence.label ?: presence.name} has left the ${location}", recipients)

View File

@@ -67,7 +67,7 @@ def updated() {
} }
def subscribe() { def subscribe() {
// log.debug "present: ${cars.collect{it.displayName + ': ' + it.currentPresence}}" log.debug "present: ${cars.collect{it.displayName + ': ' + it.currentPresence}}"
subscribe(doorSensor, "contact", garageDoorContact) subscribe(doorSensor, "contact", garageDoorContact)
subscribe(cars, "presence", carPresence) subscribe(cars, "presence", carPresence)

View File

@@ -71,7 +71,7 @@ def updated() {
private subscribeToEvents() private subscribeToEvents()
{ {
subscribe intrusionMotions, "motion", intruderMotion subscribe intrusionMotions, "motion", intruderMotion
// subscribe residentMotions, "motion", residentMotion subscribe residentMotions, "motion", residentMotion
subscribe intrusionContacts, "contact", contact subscribe intrusionContacts, "contact", contact
subscribe alarms, "alarm", alarm subscribe alarms, "alarm", alarm
subscribe(app, appTouch) subscribe(app, appTouch)
@@ -156,7 +156,6 @@ def residentMotion(evt)
// startReArmSequence() // startReArmSequence()
// } // }
//} //}
unsubscribe(residentMotions)
} }
def contact(evt) def contact(evt)
@@ -215,7 +214,7 @@ def checkForReArm()
} }
else { else {
log.warn "checkForReArm: lastIntruderMotion was null, unable to check for re-arming intrusion detection" log.warn "checkForReArm: lastIntruderMotion was null, unable to check for re-arming intrusion detection"
} }
} }
private startAlarmSequence() private startAlarmSequence()

View File

@@ -48,7 +48,7 @@ def updated()
def contactOpenHandler(evt) { def contactOpenHandler(evt) {
log.trace "$evt.value: $evt, $settings" log.trace "$evt.value: $evt, $settings"
log.debug "$contact1 was opened, sending text" log.debug "$contact1 was opened, texting $phone1"
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients) sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients)
} }

View File

@@ -50,7 +50,7 @@ def updated() {
def motionActiveHandler(evt) { def motionActiveHandler(evt) {
log.trace "$evt.value: $evt, $settings" log.trace "$evt.value: $evt, $settings"
if (presence1.latestValue("presence") == "not present") { if (presence1.latestValue("presence") == "not present") {
// Don't send a continuous stream of text messages // Don't send a continuous stream of text messages
def deltaSeconds = 10 def deltaSeconds = 10
@@ -60,14 +60,14 @@ def motionActiveHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1 def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent within the last $deltaSeconds seconds" log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
} else { } else {
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
log.debug "$motion1 has moved while you were out, sending notifications to: ${recipients?.size()}" log.debug "$motion1 has moved while you were out, sending notifications to: ${recipients?.size()}"
sendNotificationToContacts("${motion1.label} ${motion1.name} moved while you were out", recipients) sendNotificationToContacts("${motion1.label} ${motion1.name} moved while you were out", recipients)
} }
else { else {
log.debug "$motion1 has moved while you were out, sending text" log.debug "$motion1 has moved while you were out, texting $phone1"
sendSms(phone1, "${motion1.label} ${motion1.name} moved while you were out") sendSms(phone1, "${motion1.label} ${motion1.name} moved while you were out")
} }
} }

View File

@@ -53,13 +53,13 @@ def accelerationActiveHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1 def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent to phone within the last $deltaSeconds seconds" log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
} else { } else {
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts("Gun case has moved!", recipients) sendNotificationToContacts("Gun case has moved!", recipients)
} }
else { else {
log.debug "$accelerationSensor has moved, texting phone" log.debug "$accelerationSensor has moved, texting $phone1"
sendSms(phone1, "Gun case has moved!") sendSms(phone1, "Gun case has moved!")
} }
} }

View File

@@ -60,7 +60,7 @@ def authPage() {
def oauthInitUrl() { def oauthInitUrl() {
def token = getToken() def token = getToken()
//log.debug "initiateOauth got token: $token" log.debug "initiateOauth got token: $token"
// store these for validate after the user takes the oauth journey // store these for validate after the user takes the oauth journey
state.oauth_request_token = token.oauth_token state.oauth_request_token = token.oauth_token
@@ -76,7 +76,7 @@ def getToken() {
] ]
def requestTokenBaseUrl = "https://oauth.withings.com/account/request_token" def requestTokenBaseUrl = "https://oauth.withings.com/account/request_token"
def url = buildSignedUrl(requestTokenBaseUrl, params) def url = buildSignedUrl(requestTokenBaseUrl, params)
//log.debug "getToken - url: $url" log.debug "getToken - url: $url"
return getJsonFromUrl(url) return getJsonFromUrl(url)
} }
@@ -182,7 +182,7 @@ def exchangeToken() {
def requestTokenBaseUrl = "https://oauth.withings.com/account/access_token" def requestTokenBaseUrl = "https://oauth.withings.com/account/access_token"
def url = buildSignedUrl(requestTokenBaseUrl, params, tokenSecret) def url = buildSignedUrl(requestTokenBaseUrl, params, tokenSecret)
//log.debug "signed url: $url with secret $tokenSecret" log.debug "signed url: $url with secret $tokenSecret"
def token = getJsonFromUrl(url) def token = getJsonFromUrl(url)
@@ -198,8 +198,8 @@ def exchangeToken() {
def load() { def load() {
def json = get(getMeasurement(new Date() - 30)) def json = get(getMeasurement(new Date() - 30))
// removed logging of actual json payload. Can be put back for debugging
log.debug "swapped, then received json" log.debug "swapped, then received: $json"
parse(data:json) parse(data:json)
def html = """ def html = """