Compare commits

..

2 Commits

Author SHA1 Message Date
Amol Mundayoor
9dd201be3b Adding changes mentioned by @strelitzia123 2016-11-22 10:49:51 -08:00
Brian Steere
9433598381 Update netatmo-connect to use non-deprecated api
devicedata has been deprecated in favor of getstationsdata and will be removed at the end of November.

*Note* I haven't actually tested this change due to time limitations. Someone should check this out and verify it behaves properly before merging.
2016-10-14 10:22:36 -05:00
46 changed files with 419 additions and 2211 deletions

View File

@@ -1,394 +0,0 @@
/**
* Trend Setter - Colorful Light Group Device
*
* Copyright 2015 Chris Kitch
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Colorful Light Group Device", namespace: "kriskit.trendSetter", author: "Chris Kitch") {
capability "Actuator"
capability "Sensor"
capability "Switch"
capability "Switch Level"
capability "Color Control"
command "adjustLevel"
command "adjustSaturation"
command "adjustHue"
attribute "onPercentage", "number"
attribute "levelSync", "string"
attribute "colorSync", "string"
attribute "saturationSync", "string"
attribute "hueSync", "string"
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label: '${name}', action: "switch.off", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "off", label: '${name}', action: "switch.on", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#ffffff", nextState: "turningOn"
attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#ffffff", nextState: "turningOn"
attributeState "half", label: '${name}', action: "switch.on", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#a3d164", nextState: "turningOn"
attributeState "mostlyOn", label: 'Onish', action: "switch.on", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#79b821", nextState: "turningOn"
attributeState "mostlyOff", label: 'Offish', action: "switch.off", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#d1e5b5", nextState: "turninOff"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action: "color control.setColor"
}
tileAttribute ("device.onPercentage", key: "SECONDARY_CONTROL") {
attributeState "onPercentage", label:'${currentValue}% On'
attributeState "100", label:'All On'
attributeState "0", label:'All Off'
}
tileAttribute("device.level", key: "SLIDER_CONTROL") {
attributeState "default", label: '', action: "switch level.setLevel"
}
}
standardTile("levelLabel", "levelLable", height:1, width:1, decoration: "flat", inactiveLabel: true) {
state "default", label:"Level", unit:"", icon: "st.illuminance.illuminance.light"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
valueTile("levelValue", "device.level", inactiveLabel: true, height:1, width:1, decoration: "flat") {
state "default", label:'${currentValue}%', unit:""
}
valueTile("levelSync", "device.levelSync", height:1, width:1) {
state "default", label:' Sync ', unit:"", action: "adjustLevel", backgroundColor: "#ff9900"
state "ok", label:'', unit:"", backgroundColor: "#00b509"
}
standardTile("saturationLabel", "saturationLabel", height:1, width:1, decoration: "flat", inactiveLabel: true) {
state "default", label:"Sat", unit:"", icon: "st.Kids.kids2"
}
controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 3, inactiveLabel: false) {
state "saturation", action:"color control.setSaturation"
}
valueTile("saturationValue", "device.saturation", inactiveLabel: true, height:1, width:1, decoration: "flat") {
state "default", label:'${currentValue}%', unit:""
}
valueTile("saturationSync", "device.saturationSync", height:1, width:1) {
state "default", label:' Sync ', unit:"", action: "adjustSaturation", backgroundColor: "#ff9900"
state "ok", label:'', unit:"", backgroundColor: "#00b509"
}
standardTile("hueLabel", "hueLabel", height:1, width:1, decoration: "flat", inactiveLabel: true) {
state "default", label:"Hue", unit:"", icon: "st.Kids.kids2"
}
controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 3, inactiveLabel: false) {
state "hue", action:"color control.setHue"
}
valueTile("hueValue", "device.hue", inactiveLabel: true, height:1, width:1, decoration: "flat") {
state "default", label:'${currentValue}%', unit:""
}
valueTile("hueSync", "device.hueSync", height:1, width:1) {
state "default", label:' Sync ', unit:"", action: "adjustHue", backgroundColor: "#ff9900"
state "ok", label:'', unit:"", backgroundColor: "#00b509"
}
}
main "switch"
details([
"switch",
"levelLabel",
"levelSliderControl",
"levelValue",
"levelSync",
"saturationLabel",
"saturationSliderControl",
"saturationValue",
"saturationSync",
"hueLabel",
"hueSliderControl",
"hueValue",
"hueSync"])
}
def parse(String description) {
}
def groupSync(name, values) {
try {
"sync${name.capitalize()}"(values)
} catch(ex) {
log.error "Error executing 'sync${name.capitalize()}' method: $ex"
}
}
// SWITCH
def on() {
on(true)
}
def on(triggerGroup) {
sendEvent(name: "switch", value: "on")
sendEvent(name: "onPercentage", value: 100, displayed: false)
if (triggerGroup)
parent.performGroupCommand("on")
}
def off() {
off(true)
}
def off(triggerGroup) {
sendEvent(name: "switch", value: "off")
sendEvent(name: "onPercentage", value: 0, displayed: false)
if (triggerGroup)
parent.performGroupCommand("off")
}
def syncSwitch(values) {
log.debug "syncSwitch(): $values"
def onCount = values?.count { it == "on" }
def percentOn = (int)Math.floor((onCount / values?.size()) * 100)
log.debug "Percent On: $percentOn"
if (percentOn == 0 || percentOn == 100) {
if (percentOn == 0)
off(false)
else
on(false)
return
}
def value = null
if (percentOn == 50)
value = "half"
else if (percentOn > 0 && percentOn < 50)
value = "mostlyOff"
else if (percentOn > 50 && percentOn < 100)
value = "mostlyOn"
sendEvent(name: "switch", value: value)
sendEvent(name: "onPercentage", value: percentOn, displayed: false)
}
// LEVEL
def setLevel(val) {
setLevel(val, true)
}
def setLevel(val, triggerGroup) {
log.debug "Setting level to $val"
if (val < 0)
val = 0
if( val > 100)
val = 100
if (triggerGroup) {
if (val == 0)
off()
else
on()
}
sendEvent(name: "level", value: val, isStateChange: true)
sendEvent(name: "switch.setLevel", value: val, isStateChange: true)
if (triggerGroup)
parent.performGroupCommand("setLevel", [val])
}
def syncLevel(values) {
log.debug "syncLevel(): $values"
def valueCount = values?.size()
def valueCountBy = values?.countBy { it }
def matchValue = "bad"
def level = device.currentValue("level")
valueCountBy.each { value, count ->
if (count == valueCount) {
level = value
matchValue = "ok"
return true
}
}
if (matchValue == "bad")
level = getAdjustmentLevel(values)
setLevel(level, false)
sendEvent(name: "levelSync", value: matchValue, displayed: false)
}
def adjustLevel() {
def values = parent.getGroupCurrentValues("level")
if (!values)
return
def level = getAdjustmentLevel(values)
setLevel(level)
}
def getAdjustmentLevel(values) {
if (!values)
return
def valueCountBy = values?.countBy { it }
valueCountBy = valueCountBy?.sort { a, b -> b.value <=> a.value }
def level = device.currentValue("level")
if (valueCountBy.size() > 1) {
if (valueCountBy.size() == values.size()) {
log.debug "Values are all different - making average"
level = Math.round(values.sum() / values.size())
} else {
log.debug "Some values are the same, choosing most popular"
def firstItem = valueCountBy.find { true }
level = firstItem.key
}
}
return level
}
// COLOR
def setColor(value) {
setColor(value, true)
}
def setColor(value, triggerGroup) {
value.level = null
def hex = value.hex
if (!hex && value.hue && value.saturation)
hex = colorUtil.hslToHex(value.hue, value.saturation)
sendEvent(name: "color", value: value.hex, displayed:false)
if (triggerGroup)
parent.performGroupCommand("setColor", [value])
if (value.saturation)
setSaturation(value.saturation, triggerGroup, false)
if (value.hue)
setHue(value.hue, triggerGroup, false)
}
def syncColor(values) {
log.debug "syncColor(): $values"
}
// SATURATION
def setSaturation(value) {
setSaturation(value, true, true)
}
def setSaturation(value, triggerGroup, sendColor) {
on(triggerGroup)
sendEvent(name: "saturation", value: (int)value, displayed:false)
if (triggerGroup)
parent.performGroupCommand("setSaturation", [value])
if (sendColor) {
def hex = colorUtil.hslToHex((int)device.currentValue("hue"), value)
sendEvent(name: "color", value: hex, displayed:false)
}
}
def syncSaturation(values) {
log.debug "syncSaturation(): $values"
def valueCount = values?.size()
def valueCountBy = values?.countBy { it }
def matchValue = "bad"
valueCountBy.each { value, count ->
if (count == valueCount) {
matchValue = "ok"
return true
}
}
sendEvent(name: "saturationSync", value: matchValue, displayed: false)
}
def adjustSaturation() {
def saturation = (int)device.currentValue("saturation")
log.debug "adjustSaturation $saturation"
setSaturation(saturation)
}
// HUE
def setHue(value) {
setHue(value, true, true)
}
def setHue(value, triggerGroup, sendColor) {
on(triggerGroup)
sendEvent(name: "hue", value: (int)value, displayed: false)
if (triggerGroup)
parent.performGroupCommand("setHue", [value])
if (sendColor) {
def hex = colorUtil.hslToHex(value, (int)device.currentValue("saturation"))
sendEvent(name: "color", value: hex, displayed:false)
}
}
def syncHue(values) {
log.debug "syncHue(): $values"
def valueCount = values?.size()
def valueCountBy = values?.countBy { it }
def matchValue = "bad"
valueCountBy.each { value, count ->
if (count == valueCount) {
matchValue = "ok"
return true
}
}
sendEvent(name: "hueSync", value: matchValue, displayed: false)
}
def adjustHue() {
def hue = (int)device.currentValue("hue")
log.debug "adjustHue: $hue"
setHue(hue)
}

View File

@@ -1,229 +0,0 @@
/**
* Trend Setter - Dimmer Group Device
*
* Copyright 2015 Chris Kitch
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Dimmer Group Device", namespace: "kriskit.trendSetter", author: "Chris Kitch") {
capability "Actuator"
capability "Sensor"
capability "Switch"
capability "Switch Level"
command "adjustLevel"
attribute "onPercentage", "number"
attribute "levelSync", "string"
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label: '${name}', action: "switch.off", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "off", label: '${name}', action: "switch.on", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#ffffff", nextState: "turningOn"
attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#ffffff", nextState: "turningOn"
attributeState "half", label: '${name}', action: "switch.on", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#a3d164", nextState: "turningOn"
attributeState "mostlyOn", label: 'Onish', action: "switch.on", icon: "st.lights.multi-light-bulb-on", backgroundColor: "#79b821", nextState: "turningOn"
attributeState "mostlyOff", label: 'Offish', action: "switch.off", icon: "st.lights.multi-light-bulb-off", backgroundColor: "#d1e5b5", nextState: "turninOff"
}
tileAttribute ("device.onPercentage", key: "SECONDARY_CONTROL") {
attributeState "onPercentage", label:'${currentValue}% On'
attributeState "100", label:'All On'
attributeState "0", label:'All Off'
}
tileAttribute("device.level", key: "SLIDER_CONTROL") {
attributeState "default", label: '', action: "switch level.setLevel"
}
}
standardTile("levelLabel", "levelLable", height:1, width:1, decoration: "flat", inactiveLabel: true) {
state "default", label:"Level", unit:"", icon: "st.illuminance.illuminance.bright"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
valueTile("levelValue", "device.level", inactiveLabel: true, height:1, width:1, decoration: "flat") {
state "default", label:'${currentValue}%', unit:""
}
valueTile("levelSync", "device.levelSync", height:1, width:1) {
state "default", label:' Sync ', unit:"", action: "adjustLevel", backgroundColor: "#ff9900"
state "ok", label:'', unit:"", backgroundColor: "#00b509"
}
}
main "switch"
details(["switch", "levelLabel", "levelSliderControl", "levelValue", "levelSync"])
}
def parse(String description) {
}
def groupSync(name, values) {
try {
"sync${name.capitalize()}"(values)
} catch(ex) {
log.error "Error executing 'sync${name.capitalize()}' method: $ex"
}
}
// SWITCH
def on() {
on(true)
}
def on(triggerGroup) {
sendEvent(name: "switch", value: "on")
sendEvent(name: "onPercentage", value: 100, displayed: false)
if (triggerGroup)
parent.performGroupCommand("on")
}
def off() {
off(true)
}
def off(triggerGroup) {
sendEvent(name: "switch", value: "off")
sendEvent(name: "onPercentage", value: 0, displayed: false)
if (triggerGroup)
parent.performGroupCommand("off")
}
def syncSwitch(values) {
log.debug "syncSwitch(): $values"
def onCount = values?.count { it == "on" }
def percentOn = (int)Math.floor((onCount / values?.size()) * 100)
log.debug "Percent On: $percentOn"
if (percentOn == 0 || percentOn == 100) {
if (percentOn == 0)
off(false)
else
on(false)
return
}
def value = null
if (percentOn == 50)
value = "half"
else if (percentOn > 0 && percentOn < 50)
value = "mostlyOff"
else if (percentOn > 50 && percentOn < 100)
value = "mostlyOn"
sendEvent(name: "switch", value: value)
sendEvent(name: "onPercentage", value: percentOn, displayed: false)
}
// LEVEL
def setLevel(val){
setLevel(val, true)
}
def setLevel(val, triggerGroup) {
log.debug "Setting level to $val"
if (val < 0)
val = 0
if( val > 100)
val = 100
if (triggerGroup) {
if (val == 0)
off()
else
on()
}
sendEvent(name: "level", value: val, isStateChange: true)
sendEvent(name: "switch.setLevel", value: val, isStateChange: true)
if (triggerGroup)
parent.performGroupCommand("setLevel", [val])
}
def syncLevel(values) {
log.debug "syncLevel(): $values"
def valueCount = values?.size()
def valueCountBy = values?.countBy { it }
def matchValue = "bad"
def level = device.currentValue("level")
valueCountBy.each { value, count ->
if (count == valueCount) {
level = value
matchValue = "ok"
return true
}
}
if (matchValue == "bad")
level = getAdjustmentLevel(values)
setLevel(level, false)
sendEvent(name: "levelSync", value: matchValue, displayed: false)
}
def adjustLevel() {
def values = parent.getGroupCurrentValues("level")
if (!values)
return
def valueCountBy = values?.countBy { it }
valueCountBy = valueCountBy?.sort { a, b -> b.value <=> a.value }
def level = getAdjustmentLevel(values)
setLevel(level)
}
def getAdjustmentLevel(values) {
if (!values)
return
def valueCountBy = values?.countBy { it }
valueCountBy = valueCountBy?.sort { a, b -> b.value <=> a.value }
def level = device.currentValue("level")
if (valueCountBy.size() > 1) {
if (valueCountBy.size() == values.size()) {
log.debug "Values are all different - making average"
level = Math.round(values.sum() / values.size())
} else {
log.debug "Some values are the same, choosing most popular"
def firstItem = valueCountBy.find { true }
level = firstItem.key
}
}
return level
}

View File

@@ -1,162 +0,0 @@
/**
* Power Meter Group Device
*
* Copyright 2015 Chris Kitch
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Power Meter Group Device", namespace: "kriskit.trendsetter", author: "Chris Kitch") {
capability "Power Meter"
capability "Sensor"
capability "Refresh"
attribute "powerUsage", "string"
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name:"main", type: "lighting", width: 6, height: 4) {
tileAttribute ("device.powerUsage", key: "PRIMARY_CONTROL") {
attributeState "off", label: '${name}', backgroundColor: "#ffffff", icon: "st.Appliances.appliances17"
attributeState "low", label: '${name}', backgroundColor: "#5CB85C", icon: "st.Appliances.appliances17"
attributeState "medium", label: '${name}', backgroundColor: "#ff7b00", icon: "st.Appliances.appliances17"
attributeState "high", label: '${name}', backgroundColor: "#c90000", icon: "st.Appliances.appliances17"
attributeState "veryHigh", label: '${name}', backgroundColor: "#ff0000", icon: "st.Appliances.appliances17"
}
tileAttribute ("device.power", key: "SECONDARY_CONTROL") {
attributeState "default", label: '${currentValue} W'
}
}
standardTile("refresh", "refresh", height:2, width:6, inactiveLabel: false, decoration: "flat") {
state "default", action: "refresh.refresh", icon:"st.secondary.refresh"
}
main("main")
details(["main", "refresh"])
}
}
// parse events into attributes
def parse(String description) {
}
def groupSync(name, values) {
try {
"sync${name.capitalize()}"(values)
} catch(ex) {
log.error "Error executing 'sync${name.capitalize()}' method: $ex"
}
}
def refresh() {
def powerValues = parent.getGroupCurrentValues("power")
syncPower(powerValues)
}
// POWER
def syncPower(values) {
log.debug "syncPower(): $values"
def total = values?.sum()
if (total == 0) {
sendEvent(name: "power", value: 0)
sendEvent(name: "powerUsage", value: "off")
return
}
def aggregate = state.powerAggregate ?: []
state.powerSyncCount = state.powerSyncCount + 1
if (state.powerSyncCount != null && state.powerSyncCount % 5 != 0) {
aggregate.push(total)
state.powerAggregate = aggregate
return
}
def aggregatedAverage = getAverage(aggregate)
log.debug "Aggregated Average Power: $aggregatedAverage"
sendEvent(name: "power", value: aggregatedAverage)
def level = getPowerUsageLevel(aggregatedAverage)
log.debug "Power usage level: $level"
sendEvent(name: "powerUsage", value: level)
state.powerAggregate = []
}
def getPowerUsageLevel(value) {
if (value == 0)
return "off"
def boundaries = getPowerUsageBoundaries()
if (!boundaries)
return "low"
log.debug "Determining power usage level with boundaries: $boundaries for value $value"
if (value > 0 && value <= boundaries.bottom)
return "low"
if (value > boundaries.bottom && value < boundaries.top)
return "medium"
if (value >= boundaries.top && value <= boundaries.max)
return "high"
if (value > boundaries.max) {
state.powerUsageBoundaries = null
return "veryHigh"
}
}
def getPowerUsageBoundaries() {
if (state.powerUsageBoundaries && state.powerSyncCount < 100)
return state.powerUsageBoundaries
def events = device.events([max: 500])
def powerEvents = events?.findAll {
it.name == "power" && it.doubleValue > 0
}
def powerValues = powerEvents*.doubleValue
powerValues.sort()
def eventCount = powerValues?.size()
def eventChunkSize = (int)Math.round(eventCount / 2)
def chunkedEvents = powerValues.collate(eventChunkSize)
if (chunkedEvents.size() < 2)
return null
def boundaries = [
top: getAverage(chunkedEvents[1]),
bottom: getAverage(chunkedEvents[0]),
max: powerValues.max()
]
state.powerSyncCount = 0
state.powerUsageBoundaries = boundaries
log.debug "New boundaries: $boundaries"
return boundaries
}
def getAverage(values) {
return Math.round((values.sum() / values.size()) * 100) / 100
}

View File

@@ -1,116 +0,0 @@
/**
* Trend Setter - Switch Group Device
*
* Copyright 2015 Chris Kitch
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Switch Group Device", namespace: "kriskit.trendSetter", author: "Chris Kitch") {
capability "Actuator"
capability "Sensor"
capability "Switch"
attribute "onPercentage", "number"
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
attributeState "half", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#a3d164", nextState: "turningOn"
attributeState "mostlyOn", label: 'Onish', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#79b821", nextState: "turningOn"
attributeState "mostlyOff", label: 'Offish', action: "switch.off", icon: "st.switches.switch.off", backgroundColor: "#d1e5b5", nextState: "turninOff"
}
tileAttribute ("device.onPercentage", key: "SECONDARY_CONTROL") {
attributeState "onPercentage", label:'${currentValue}% On'
attributeState "100", label:'All On'
attributeState "0", label:'All Off'
}
}
}
main "switch"
details(["switch"])
}
def parse(String description) {
}
def groupSync(name, values) {
try {
"sync${name.capitalize()}"(values)
} catch(ex) {
log.error "Error executing 'sync${name.capitalize()}' method: $ex"
}
}
// SWITCH
def on() {
on(true)
}
def on(triggerGroup) {
sendEvent(name: "switch", value: "on")
sendEvent(name: "onPercentage", value: 100, displayed: false)
if (triggerGroup)
parent.performGroupCommand("on")
}
def off() {
off(true)
}
def off(triggerGroup) {
sendEvent(name: "switch", value: "off")
sendEvent(name: "onPercentage", value: 0, displayed: false)
if (triggerGroup)
parent.performGroupCommand("off")
}
def syncSwitch(values) {
log.debug "syncSwitch(): $values"
def onCount = values?.count { it == "on" }
def percentOn = (int)Math.floor((onCount / values?.size()) * 100)
log.debug "Percent On: $percentOn"
if (percentOn == 0 || percentOn == 100) {
if (percentOn == 0)
off(false)
else
on(false)
return
}
def value = null
if (percentOn == 50)
value = "half"
else if (percentOn > 0 && percentOn < 50)
value = "mostlyOff"
else if (percentOn > 50 && percentOn < 100)
value = "mostlyOn"
sendEvent(name: "switch", value: value)
sendEvent(name: "onPercentage", value: percentOn, displayed: false)
}

View File

@@ -59,15 +59,6 @@ metadata {
}
}
def installed() {
log.debug "${device} installed"
}
def updated() {
log.debug "${device} updated"
configureHealthCheck()
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
@@ -105,14 +96,6 @@ def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh()
}
def configureHealthCheck() {
log.debug "configureHealthCheck"
unschedule("healthPoll")
runEvery5Minutes("healthPoll")
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
def healthPoll() {
log.debug "healthPoll()"
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh()
@@ -120,5 +103,9 @@ def healthPoll() {
}
def configure() {
refresh()
unschedule()
runEvery5Minutes("healthPoll")
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
zigbee.onOffRefresh() + zigbee.levelRefresh()
}

View File

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

View File

@@ -1,45 +0,0 @@
# Z-wave Dimmer Switch
Works with:
* [GE Z-Wave In-Wall Smart Dimmer (GE 12724)](http://products.z-wavealliance.org/products/1197)
* [GE Z-Wave In-Wall Smart Dimmer (Toggle) (GE 12729)](http://products.z-wavealliance.org/products/1201)
* [GE Z-Wave Plug-in Smart Dimmer (GE 12718)](http://products.z-wavealliance.org/products/1191)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Troubleshooting](#Troubleshooting)
## Capabilities
* **Switch Level** - it's defined to accept two parameters, the level and the rate of dimming
* **Actuator** - represents that a Device has commands
* **Indicator** - gives you the ability to set the indicator LED light on a Z-Wave switch
* **Switch** - can detect state (possible values: on/off)
* **Polling** - represents that poll() can be implemented for the device
* **Refresh** - _refresh()_ command for status updates
* **Sensor** - detects sensor events
* **Health Check** - indicates ability to get device health notifications
## Device Health
Z-Wave Smart Dimmers (In-Wall, In-Wall(Toggle), Plug-In) are polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
Check-in interval = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [General Z-Wave Dimmer/Switch Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200955890-Troubleshooting-GE-in-wall-switch-or-dimmer-won-t-respond-to-commands-or-automations-Z-Wave-)
* [GE Z-Wave In-Wall Smart Dimmer (GE 12724) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200902600-GE-In-Wall-Paddle-Dimmer-Switch-GE-12724-Z-Wave-)
* [GE Z-Wave In-Wall Smart Dimmer (Toggle) (GE 12729) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/207568463-GE-In-Wall-Smart-Toggle-Dimmer-GE-12729-Z-Wave-)
* [GE Z-Wave Plug-in Smart Dimmer (GE 12718) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202088474-GE-Plug-In-Smart-Dimmer-GE-12718-Z-Wave-)

View File

@@ -20,7 +20,6 @@ metadata {
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Health Check"
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "Z-Wave Wall Dimmer"
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "Z-Wave Wall Dimmer"
@@ -83,8 +82,6 @@ metadata {
}
def updated(){
// Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
switch (ledIndicator) {
case "on":
indicatorWhenOn()
@@ -218,13 +215,6 @@ def poll() {
zwave.switchMultilevelV1.switchMultilevelGet().format()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
refresh()
}
def refresh() {
log.debug "refresh() is called"
def commands = []

View File

@@ -148,12 +148,14 @@ def generateEvent(Map results) {
handlerName: name]
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange
event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed]
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
event << [value: sendValue, unit: temperatureScale, displayed: false]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
isChange = isStateChange(device, name, value.toString())
@@ -251,6 +253,7 @@ void setCoolingSetpoint(setpoint) {
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if (coolingSetpoint > maxCoolingSetpoint) {
coolingSetpoint = maxCoolingSetpoint
} else if (coolingSetpoint < minCoolingSetpoint) {
@@ -280,6 +283,7 @@ void setCoolingSetpoint(setpoint) {
}
void resumeProgram() {
log.debug "resumeProgram() is called"
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
def deviceId = device.deviceNetworkId.split(/\./).last()
@@ -350,6 +354,7 @@ def switchFanMode() {
}
def switchToFanMode(nextMode) {
log.debug "switching to fan mode: $nextMode"
def returnCommand
@@ -515,56 +520,63 @@ def fanAuto() {
}
def generateSetpointEvent() {
log.debug "Generate SetPoint Event"
def mode = device.currentValue("thermostatMode")
log.debug "Current Mode = ${mode}"
def heatingSetpoint = device.currentValue("heatingSetpoint")
log.debug "Heating Setpoint = ${heatingSetpoint}"
def coolingSetpoint = device.currentValue("coolingSetpoint")
log.debug "Cooling Setpoint = ${coolingSetpoint}"
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if(location.temperatureScale == "C") {
maxHeatingSetpoint = maxHeatingSetpoint > 40 ? roundC(convertFtoC(maxHeatingSetpoint)) : roundC(maxHeatingSetpoint)
maxCoolingSetpoint = maxCoolingSetpoint > 40 ? roundC(convertFtoC(maxCoolingSetpoint)) : roundC(maxCoolingSetpoint)
minHeatingSetpoint = minHeatingSetpoint > 40 ? roundC(convertFtoC(minHeatingSetpoint)) : roundC(minHeatingSetpoint)
minCoolingSetpoint = minCoolingSetpoint > 40 ? roundC(convertFtoC(minCoolingSetpoint)) : roundC(minCoolingSetpoint)
heatingSetpoint = heatingSetpoint > 40 ? roundC(convertFtoC(heatingSetpoint)) : roundC(heatingSetpoint)
coolingSetpoint = coolingSetpoint > 40 ? roundC(convertFtoC(coolingSetpoint)) : roundC(coolingSetpoint)
} else {
maxHeatingSetpoint = maxHeatingSetpoint < 40 ? roundC(convertCtoF(maxHeatingSetpoint)) : maxHeatingSetpoint
maxCoolingSetpoint = maxCoolingSetpoint < 40 ? roundC(convertCtoF(maxCoolingSetpoint)) : maxCoolingSetpoint
minHeatingSetpoint = minHeatingSetpoint < 40 ? roundC(convertCtoF(minHeatingSetpoint)) : minHeatingSetpoint
minCoolingSetpoint = minCoolingSetpoint < 40 ? roundC(convertCtoF(minCoolingSetpoint)) : minCoolingSetpoint
heatingSetpoint = heatingSetpoint < 40 ? roundC(convertCtoF(heatingSetpoint)) : heatingSetpoint
coolingSetpoint = coolingSetpoint < 40 ? roundC(convertCtoF(coolingSetpoint)) : coolingSetpoint
if(location.temperatureScale == "C")
{
maxHeatingSetpoint = roundC(maxHeatingSetpoint)
maxCoolingSetpoint = roundC(maxCoolingSetpoint)
minHeatingSetpoint = roundC(minHeatingSetpoint)
minCoolingSetpoint = roundC(minCoolingSetpoint)
heatingSetpoint = roundC(heatingSetpoint)
coolingSetpoint = roundC(coolingSetpoint)
}
log.debug "Current Mode = ${mode}"
log.debug "Heating Setpoint = ${heatingSetpoint}"
log.debug "Cooling Setpoint = ${coolingSetpoint}"
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
}
else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
} else if (mode == "auto") {
sendEvent("name":"thermostatSetpoint", "value":"Auto")
} else if (mode == "off") {
sendEvent("name":"thermostatSetpoint", "value":"Off")
} else if (mode == "auxHeatOnly") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
}
}
void raiseSetpoint() {
@@ -573,31 +585,21 @@ void raiseSetpoint() {
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow raiseSetpoint"
} else {
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
if (location.temperatureScale == "C") {
maxHeatingSetpoint = maxHeatingSetpoint > 40 ? convertFtoC(maxHeatingSetpoint) : maxHeatingSetpoint
maxCoolingSetpoint = maxCoolingSetpoint > 40 ? convertFtoC(maxCoolingSetpoint) : maxCoolingSetpoint
heatingSetpoint = heatingSetpoint > 40 ? convertFtoC(heatingSetpoint) : heatingSetpoint
coolingSetpoint = coolingSetpoint > 40 ? convertFtoC(coolingSetpoint) : coolingSetpoint
thermostatSetpoint = thermostatSetpoint > 40 ? convertFtoC(thermostatSetpoint) : thermostatSetpoint
} else {
maxHeatingSetpoint = maxHeatingSetpoint < 40 ? convertCtoF(maxHeatingSetpoint) : maxHeatingSetpoint
maxCoolingSetpoint = maxCoolingSetpoint < 40 ? convertCtoF(maxCoolingSetpoint) : maxCoolingSetpoint
heatingSetpoint = heatingSetpoint < 40 ? convertCtoF(heatingSetpoint) : heatingSetpoint
coolingSetpoint = coolingSetpoint < 40 ? convertCtoF(coolingSetpoint) : coolingSetpoint
thermostatSetpoint = thermostatSetpoint < 40 ? convertCtoF(thermostatSetpoint) : thermostatSetpoint
}
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
} else {
targetvalue = 0
}
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
@@ -620,29 +622,20 @@ void lowerSetpoint() {
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow lowerSetpoint"
} else {
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
if (location.temperatureScale == "C") {
minHeatingSetpoint = minHeatingSetpoint > 40 ? convertFtoC(minHeatingSetpoint) : minHeatingSetpoint
minCoolingSetpoint = minCoolingSetpoint > 40 ? convertFtoC(minCoolingSetpoint) : minCoolingSetpoint
heatingSetpoint = heatingSetpoint > 40 ? convertFtoC(heatingSetpoint) : heatingSetpoint
coolingSetpoint = coolingSetpoint > 40 ? convertFtoC(coolingSetpoint) : coolingSetpoint
thermostatSetpoint = thermostatSetpoint > 40 ? convertFtoC(thermostatSetpoint) : thermostatSetpoint
} else {
minHeatingSetpoint = minHeatingSetpoint < 40 ? convertCtoF(minHeatingSetpoint) : minHeatingSetpoint
minCoolingSetpoint = minCoolingSetpoint < 40 ? convertCtoF(minCoolingSetpoint) : minCoolingSetpoint
heatingSetpoint = heatingSetpoint < 40 ? convertCtoF(heatingSetpoint) : heatingSetpoint
coolingSetpoint = coolingSetpoint < 40 ? convertCtoF(coolingSetpoint) : coolingSetpoint
thermostatSetpoint = thermostatSetpoint < 40 ? convertCtoF(thermostatSetpoint) : thermostatSetpoint
}
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
} else {
targetvalue = 0
}
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
@@ -660,6 +653,7 @@ void lowerSetpoint() {
//called by raiseSetpoint() and lowerSetpoint()
void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode")
if (mode == "off" || mode == "auto") {
@@ -672,18 +666,6 @@ void alterSetpoint(temp) {
def targetHeatingSetpoint
def targetCoolingSetpoint
def temperatureScaleHasChanged = false
if (location.temperatureScale == "C") {
if ( heatingSetpoint > 40.0 || coolingSetpoint > 40.0 ) {
temperatureScaleHasChanged = true
}
} else {
if ( heatingSetpoint < 40.0 || coolingSetpoint < 40.0 ) {
temperatureScaleHasChanged = true
}
}
//step1: check thermostatMode, enforce limits before sending request to cloud
if (mode == "heat" || mode == "auxHeatOnly"){
if (temp.value > coolingSetpoint){
@@ -725,18 +707,17 @@ void alterSetpoint(temp) {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
}
}
if ( temperatureScaleHasChanged )
generateSetpointEvent()
generateStatusEvent()
}
}
def generateStatusEvent() {
def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def temperature = device.currentValue("temperature")
def statusText
log.debug "Generate Status Event for Mode = ${mode}"
@@ -746,25 +727,36 @@ def generateStatusEvent() {
log.debug "HVAC Mode = ${mode}"
if (mode == "heat") {
if (temperature >= heatingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
} else if (mode == "cool") {
if (temperature <= coolingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
} else if (mode == "auto") {
statusText = "Right Now: Auto"
} else if (mode == "off") {
statusText = "Right Now: Off"
} else if (mode == "auxHeatOnly") {
statusText = "Emergency Heat"
} else {
statusText = "?"
}
} else if (mode == "auto") {
statusText = "Right Now: Auto"
} else if (mode == "off") {
statusText = "Right Now: Off"
} else if (mode == "auxHeatOnly") {
statusText = "Emergency Heat"
} else {
statusText = "?"
}
log.debug "Generate Status Event = ${statusText}"
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
}
@@ -778,7 +770,7 @@ def roundC (tempC) {
}
def convertFtoC (tempF) {
return ((Math.round(((tempF - 32)*(5/9)) * 2))/2).toDouble()
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
}
def convertCtoF (tempC) {

View File

@@ -57,7 +57,7 @@ metadata {
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
}
// parse events into attributes
@@ -172,3 +172,6 @@ def verifyPercent(percent) {
}
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -45,7 +45,7 @@ metadata {
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
@@ -87,3 +87,6 @@ def parse(description) {
results
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -66,7 +66,7 @@ metadata {
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
}
// parse events into attributes
@@ -188,3 +188,6 @@ def verifyPercent(percent) {
}
}
def ping() {
log.trace "${parent.ping(this)}"
}

View File

@@ -50,7 +50,7 @@ metadata {
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
}
// parse events into attributes
@@ -93,3 +93,6 @@ void refresh() {
parent.manualRefresh()
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -55,7 +55,7 @@ metadata {
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
}
// parse events into attributes
@@ -107,3 +107,6 @@ void refresh() {
parent.manualRefresh()
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -11,9 +11,9 @@ metadata {
capability "Color Temperature"
capability "Switch"
capability "Switch Level" // brightness
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Health Check"
}
simulator {
@@ -23,6 +23,7 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
@@ -63,8 +64,12 @@ metadata {
}
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}")
// parse events into attributes
def parse(String description) {
if (description == 'updated') {
return // don't poll when config settings is being updated as it may time out
}
poll()
}
// handle commands
@@ -136,6 +141,7 @@ def setLevel(percentage) {
percentage = 1 // clamp to 1%
}
if (percentage == 0) {
sendEvent(name: "level", value: 0) // Otherwise the level value tile does not update
return off() // if the brightness is set to 0, just turn it off
}
parent.logErrors(logObject:log) {
@@ -187,17 +193,14 @@ def off() {
return []
}
def refresh() {
log.debug "Executing 'refresh'"
def poll() {
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
def resp = parent.apiGET("/lights/${selector()}")
if (resp.status == 404) {
state.online = false
sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false)
log.warn "$device is Offline"
sendEvent(name: "switch", value: "unreachable")
return []
} else if (resp.status != 200) {
log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}")
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
return []
}
def data = resp.data[0]
@@ -206,20 +209,19 @@ def refresh() {
sendEvent(name: "label", value: data.label)
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch", value: data.power)
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int))
sendEvent(name: "hue", value: data.color.hue / 3.6)
sendEvent(name: "saturation", value: data.color.saturation * 100)
sendEvent(name: "colorTemperature", value: data.color.kelvin)
sendEvent(name: "model", value: data.product.name)
sendEvent(name: "model", value: "${data.product.company} ${data.product.name}")
if (data.connected) {
sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false)
log.debug "$device is Online"
} else {
sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false)
log.warn "$device is Offline"
return []
}
def refresh() {
log.debug "Executing 'refresh'"
poll()
}
def selector() {

View File

@@ -10,9 +10,9 @@ metadata {
capability "Color Temperature"
capability "Switch"
capability "Switch Level" // brightness
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Health Check"
}
simulator {
@@ -22,12 +22,13 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
}
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
@@ -52,10 +53,15 @@ metadata {
main "switch"
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}")
// parse events into attributes
def parse(String description) {
if (description == 'updated') {
return // don't poll when config settings is being updated as it may time out
}
poll()
}
// handle commands
@@ -65,6 +71,7 @@ def setLevel(percentage) {
percentage = 1 // clamp to 1%
}
if (percentage == 0) {
sendEvent(name: "level", value: 0) // Otherwise the level value tile does not update
return off() // if the brightness is set to 0, just turn it off
}
parent.logErrors(logObject:log) {
@@ -116,17 +123,14 @@ def off() {
return []
}
def refresh() {
log.debug "Executing 'refresh'"
def poll() {
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
def resp = parent.apiGET("/lights/${selector()}")
if (resp.status == 404) {
state.online = false
sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false)
log.warn "$device is Offline"
sendEvent(name: "switch", value: "unreachable")
return []
} else if (resp.status != 200) {
log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}")
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
return []
}
def data = resp.data[0]
@@ -134,17 +138,16 @@ def refresh() {
sendEvent(name: "label", value: data.label)
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch", value: data.power)
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
sendEvent(name: "colorTemperature", value: data.color.kelvin)
sendEvent(name: "model", value: data.product.name)
if (data.connected) {
sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false)
log.debug "$device is Online"
} else {
sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false)
log.warn "$device is Offline"
}
return []
}
def refresh() {
log.debug "Executing 'refresh'"
poll()
}
def selector() {

View File

@@ -74,15 +74,6 @@ metadata {
}
}
def installed() {
log.debug "${device} installed"
}
def updated() {
log.debug "${device} updated"
configureHealthCheck()
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
@@ -149,14 +140,10 @@ def refresh() {
zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh()
}
def configureHealthCheck() {
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
def configure() {
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
refresh() + zigbee.onOffConfig(0, 300) + powerConfig()

View File

@@ -84,15 +84,6 @@ metadata {
}
}
def installed() {
log.debug "${device} installed"
}
def updated() {
log.debug "${device} updated"
configureHealthCheck()
}
def parse(String description) {
log.debug "description: $description"
@@ -312,13 +303,11 @@ def refresh() {
return refreshCmds + enrollResponse()
}
def configureHealthCheck() {
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
def configure() {
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config

View File

@@ -88,15 +88,6 @@ metadata {
}
}
def installed() {
log.debug "${device} installed"
}
def updated() {
log.debug "${device} updated"
configureHealthCheck()
}
def parse(String description) {
log.debug "description: $description"
@@ -327,13 +318,11 @@ def refresh() {
return refreshCmds + enrollResponse()
}
def configureHealthCheck() {
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
def configure() {
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config

View File

@@ -115,34 +115,6 @@ metadata {
}
}
def installed() {
log.debug "${device} installed"
}
def updated() {
log.debug "updated called"
log.info "garage value : $garageSensor"
if (garageSensor == "Yes") {
def descriptionText = "Updating device to garage sensor"
if (device.latestValue("status") == "open") {
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true)
}
else if (device.latestValue("status") == "closed") {
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true)
}
}
else {
def descriptionText = "Updating device to open/close sensor"
if (device.latestValue("status") == "garage-open") {
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true)
}
else if (device.latestValue("status") == "garage-closed") {
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true)
}
}
configureHealthCheck()
}
def parse(String description) {
Map map = [:]
if (description?.startsWith('catchall:')) {
@@ -274,6 +246,29 @@ private Map parseIasMessage(String description) {
return resultMap
}
def updated() {
log.debug "updated called"
log.info "garage value : $garageSensor"
if (garageSensor == "Yes") {
def descriptionText = "Updating device to garage sensor"
if (device.latestValue("status") == "open") {
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true)
}
else if (device.latestValue("status") == "closed") {
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true)
}
}
else {
def descriptionText = "Updating device to open/close sensor"
if (device.latestValue("status") == "garage-open") {
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true)
}
else if (device.latestValue("status") == "garage-closed") {
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true)
}
}
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
@@ -416,13 +411,11 @@ def refresh() {
return refreshCmds + enrollResponse()
}
def configureHealthCheck(){
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
def configure() {
log.debug "Configuring Reporting"
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity

View File

@@ -75,15 +75,6 @@ metadata {
}
}
def installed() {
log.debug "${device} installed"
}
def updated() {
log.debug "${device} updated"
configureHealthCheck()
}
def parse(String description) {
log.debug "description: $description"
@@ -274,12 +265,11 @@ def refresh() {
return refreshCmds + enrollResponse()
}
def configureHealthCheck() {
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
def configure() {
log.debug "Configuring Reporting, IAS CIE, and Bindings."
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity

View File

@@ -69,15 +69,6 @@ metadata {
}
}
def installed() {
log.debug "${device} installed"
}
def updated() {
log.debug "${device} updated"
configureHealthCheck()
}
def parse(String description) {
log.debug "description: $description"
@@ -273,17 +264,16 @@ def refresh()
{
log.debug "refresh temperature, humidity, and battery"
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware
zigbee.readAttribute(0x0402, 0x0000) +
zigbee.readAttribute(0x0001, 0x0020)
}
def configureHealthCheck() {
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
def configure() {
log.debug "Configuring Reporting and Bindings."
def humidityConfigCmds = [
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",

View File

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

View File

@@ -1,41 +0,0 @@
# GE Plug-In/In-Wall Smart Dimmer (ZigBee)
Works with:
* [GE In-Wall Smart Dimmer (ZigBee)](https://shop.smartthings.com/#!/products/ge-in-wall-smart-dimmer-switch)
* [GE Plug-In Smart Dimmer (ZigBee)](https://www.smartthings.com/works-with-smartthings/ge/ge-plug-in-smart-dimmer-zigbee)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Troubleshooting](#Troubleshooting)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Refresh** - _refresh()_ command for status updates
* **Power Meter** - ability to check the power meter(energy consumption) of device
* **Sensor** - represents the device sensor capability
* **Switch** - can detect state (possible values: on/off)
* **Switch Level** - represents current light level, usually 0-100 in percent
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Zigbee dimmer with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Enrolls with default periodic reporting until newer 5 min interval is confirmed
It then enrolls the device with updated checkInterval i.e. 12 mins
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [GE Z-Wave In-Wall Smart Dimmer (GE 45857) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/204988564-GE-In-Wall-Smart-Dimmer-45857GE-ZigBee-)
* [GE Zigbee Plug-in Smart Dimmer (GE 45852) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205239280-GE-Plug-In-Smart-Dimmer-45852GE-ZigBee-)

View File

@@ -21,7 +21,6 @@ metadata {
capability "Sensor"
capability "Switch"
capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702"
@@ -71,20 +70,8 @@ def parse(String description) {
}
}
else {
def cluster = zigbee.parse(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
}
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
}
}
@@ -100,22 +87,11 @@ def setLevel(value) {
zigbee.setLevel(value)
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.onOffRefresh()
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
refresh()
}

View File

@@ -48,15 +48,6 @@ metadata {
}
}
def installed() {
log.debug "${device} installed"
}
def updated() {
log.debug "${device} updated"
configureHealthCheck()
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
@@ -109,13 +100,12 @@ def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
}
def configureHealthCheck() {
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
def configure() {
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
}

View File

@@ -28,8 +28,8 @@ metadata {
capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB", deviceJoinName: "OSRAM LIGHTIFY Gardenspot mini RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB", deviceJoinName: "OSRAM LIGHTIFY Gardenspot mini RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB"
}
// UI tile definitions

View File

@@ -70,16 +70,6 @@ metadata {
}
}
def installed() {
log.debug "${device} installed"
}
def updated() {
log.debug "${device} updated"
configureHealthCheck()
}
//Globals
private getATTRIBUTE_HUE() { 0x0000 }
private getATTRIBUTE_SATURATION() { 0x0001 }
@@ -148,17 +138,16 @@ def ping() {
}
def refresh() {
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffRefresh() + zigbee.levelRefresh() + 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)
}
def configureHealthCheck() {
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
def configure() {
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
refresh()
}

View File

@@ -21,7 +21,6 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "0003, 0006, 0019, 0406", manufacturer: "Leviton", model: "ZSS-10", deviceJoinName: "Leviton Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "000A", manufacturer: "HAI", model: "65A21-1", deviceJoinName: "Leviton Wireless Load Control Module-30amp"
}
// simulator metadata

View File

@@ -69,15 +69,6 @@ metadata {
}
}
def installed() {
log.debug "${device} installed"
}
def updated() {
log.debug "${device} updated"
configureHealthCheck()
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
@@ -130,17 +121,16 @@ def ping() {
}
def refresh() {
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
}
def configureHealthCheck() {
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
def configure() {
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
refresh()
}

View File

@@ -11,9 +11,6 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import groovy.transform.Field
@Field Boolean hasConfiguredHealthCheck = false
metadata {
definition (name: "ZLL Dimmer Bulb", namespace: "smartthings", author: "SmartThings") {
@@ -24,7 +21,6 @@ metadata {
capability "Refresh"
capability "Switch"
capability "Switch Level"
capability "Health Check"
//fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019"
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0019"
@@ -100,38 +96,7 @@ def poll() {
refresh()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.levelRefresh()
}
def healthPoll() {
log.debug "healthPoll()"
def cmds = refresh()
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
}
def configureHealthCheck() {
Integer hcIntervalMinutes = 12
if (!hasConfiguredHealthCheck) {
log.debug "Configuring Health Check, Reporting"
unschedule("healthPoll")
runEvery5Minutes("healthPoll")
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
hasConfiguredHealthCheck = true
}
}
def configure() {
log.debug "configure()"
log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
configureHealthCheck()
}
def updated() {
log.debug "updated()"
configureHealthCheck()
}

View File

@@ -11,9 +11,6 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import groovy.transform.Field
@Field Boolean hasConfiguredHealthCheck = false
metadata {
definition (name: "ZLL White Color Temperature Bulb", namespace: "smartthings", author: "SmartThings") {
@@ -25,7 +22,6 @@ metadata {
capability "Refresh"
capability "Switch"
capability "Switch Level"
capability "Health Check"
attribute "colorName", "string"
command "setGenericName"
@@ -100,41 +96,9 @@ def poll() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.levelRefresh()
}
def healthPoll() {
log.debug "healthPoll()"
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh()
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
}
def configureHealthCheck() {
Integer hcIntervalMinutes = 12
if (!hasConfiguredHealthCheck) {
log.debug "Configuring Health Check, Reporting"
unschedule("healthPoll")
runEvery5Minutes("healthPoll")
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
hasConfiguredHealthCheck = true
}
}
def configure() {
log.debug "configure()"
configureHealthCheck()
log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
}
def updated() {
log.debug "updated()"
configureHealthCheck()
}
def setColorTemperature(value) {

View File

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

View File

@@ -1,39 +0,0 @@
# Z-wave Dimmer
Works with:
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW)](http://www.leviton.com/OA_HTML/ProductDetail.jsp?partnumber=DZPD3-1LW)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Troubleshooting](#troubleshooting)
## Capabilities
* **Switch Level** - it's defined to accept two parameters, the level and the rate of dimming
* **Actuator** - represents that a Device has commands
* **Health Check** - indicates ability to get device health notifications
* **Switch** - can detect state (possible values: on/off)
* **Polling** - represents that poll() can be implemented for the device
* **Refresh** - _refresh()_ command for status updates
* **Sensor** - detects sensor events
## Device Health
A Category C5 Leviton Plug-in Lamp Dimmer Module (DZPA1-1LW) (Z-Wave) polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)

View File

@@ -15,14 +15,12 @@ metadata {
definition (name: "Z-Wave Dimmer Switch Generic", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Health Check"
capability "Switch"
capability "Polling"
capability "Refresh"
capability "Sensor"
fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer"
fingerprint mfr:"001D", prod:"1902", deviceJoinName: "Z-Wave Dimmer"
}
simulator {
@@ -70,11 +68,6 @@ metadata {
}
}
def updated(){
// Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
}
def parse(String description) {
def result = null
if (description != "updated") {
@@ -192,13 +185,6 @@ def poll() {
zwave.switchMultilevelV1.switchMultilevelGet().format()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
refresh()
}
def refresh() {
log.debug "refresh() is called"
def commands = []

View File

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

View File

@@ -1,39 +0,0 @@
# Z-wave Switch
Works with:
* [Leviton Appliance Module (DZPA1-1LW)](https://support.smartthings.com/hc/en-us/articles/205881176-Leviton-Appliance-Module-DZPA1-1LW-)
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave)](https://support.smartthings.com/hc/en-us/articles/200903080-GE-Plug-In-Outdoor-Smart-Switch-GE-12720-Z-Wave-)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Health Check** - indicates ability to get device health notifications
* **Switch** - can detect state (possible values: on/off)
* **Polling** - represents that poll() can be implemented for the device
* **Refresh** - _refresh()_ command for status updates
* **Sensor** - detects sensor events
## Device Health
A Category C5 Leviton Appliance Module (DZPA1-1LW) and GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [Leviton Appliance Module (DZPA1-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205881176-Leviton-Appliance-Module-DZPA1-1LW-)
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903080-GE-Plug-In-Outdoor-Smart-Switch-GE-12720-Z-Wave-)

View File

@@ -14,15 +14,12 @@
metadata {
definition (name: "Z-Wave Switch Generic", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Health Check"
capability "Switch"
capability "Polling"
capability "Refresh"
capability "Sensor"
fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch"
fingerprint mfr:"001D", prod:"1A02", deviceJoinName: "Z-Wave Switch"
fingerprint mfr:"0063", prod:"4F50", deviceJoinName: "Z-Wave Switch"
}
// simulator metadata
@@ -53,11 +50,6 @@ metadata {
}
}
def updated(){
// Device-Watch simply pings if no device events received for checkInterval duration of 32min = 2 * 15min + 2min lag time
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
}
def parse(String description) {
def result = null
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
@@ -134,13 +126,6 @@ def poll() {
])
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
refresh()
}
def refresh() {
delayBetween([
zwave.switchBinaryV1.switchBinaryGet().format(),

View File

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

View File

@@ -1,49 +0,0 @@
# Z-Wave Switch
Works with:
* [GE Z-Wave Plug-In Smart Switch (12719)](http://products.z-wavealliance.org/products/1193)
* [GE Z-Wave In-Wall Smart Outlet (12721)](http://products.z-wavealliance.org/products/1195)
* [GE Z-Wave In-Wall Smart Switch (12722)](http://products.z-wavealliance.org/products/1196)
* [GE Z-Wave In-Wall Smart Toggle Switch (12727)](http://products.z-wavealliance.org/products/1200)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Troubleshooting](#Troubleshooting)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Indicator** - gives you the ability to set the indicator LED light on a Z-Wave switch
* **Switch** - can detect state (possible values: on/off)
* **Polling** - represents that poll() can be implemented for the device
* **Refresh** - _refresh()_ command for status updates
* **Sensor** - detects sensor events
* **Health Check** - indicates ability to get device health notifications
## Device Health
Z-Wave Switches (Plug-In, In-Wall(Toggle Switch, Switch, Outlet)) are polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [General Z-Wave Dimmer/Switch Troubleshooting](https://support.smartthings.com/hc/en-us/articles/200955890-Troubleshooting-GE-in-wall-switch-or-dimmer-won-t-respond-to-commands-or-automations-Z-Wave-)
* [GE Z-Wave Plug-In Smart Switch (12719) Troubleshooting](https://support.smartthings.com/hc/en-us/articles/200903070-GE-Plug-In-Smart-Switch-GE-12719-Z-Wave)
* [GE Z-Wave In-Wall Smart Outlet (12721) Troubleshooting](https://support.smartthings.com/hc/en-us/articles/200903020-GE-In-Wall-Smart-Outlet-GE-12721-Z-Wave)
* [GE Z-Wave In-Wall Smart Switch (12722) Troubleshooting](https://support.smartthings.com/hc/en-us/articles/200902540-GE-In-Wall-Smart-Switch-GE-12722-Z-Wave)
* [GE Z-Wave In-Wall Smart Toggle Switch (12727) Troubleshooting](https://support.smartthings.com/hc/en-us/articles/207568933-GE-In-Wall-Smart-Toggle-Switch-GE-12727-Z-Wave)

View File

@@ -19,7 +19,6 @@ metadata {
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Health Check"
fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch"
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
@@ -65,8 +64,6 @@ metadata {
}
def updated(){
// Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
switch (ledIndicator) {
case "on":
indicatorWhenOn()
@@ -159,13 +156,6 @@ def poll() {
])
}
/**
* PING is used by Device-Watch in attempt to reach the Device
**/
def ping() {
refresh()
}
def refresh() {
delayBetween([
zwave.switchBinaryV1.switchBinaryGet().format(),

View File

@@ -387,18 +387,18 @@ def getDeviceList() {
state.deviceDetail = [:]
state.deviceState = [:]
apiGet("/api/devicelist") { response ->
apiGet("/api/getstationsdata") { response ->
response.data.body.devices.each { value ->
def key = value._id
deviceList[key] = "${value.station_name}: ${value.module_name}"
state.deviceDetail[key] = value
state.deviceState[key] = value.dashboard_data
}
response.data.body.modules.each { value ->
def key = value._id
deviceList[key] = "${state.deviceDetail[value.main_device].station_name}: ${value.module_name}"
state.deviceDetail[key] = value
state.deviceState[key] = value.dashboard_data
value.modules.each { value2 ->
def key2 = value2._id
deviceList[key2] = "${value.station_name}: ${value2.module_name}"
state.deviceDetail[key2] = value2
state.deviceState[key2] = value2.dashboard_data
}
}
}

View File

@@ -1,317 +0,0 @@
/**
* Trend Setter - Group
*
* Copyright 2015 Chris Kitch
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Group",
namespace: "kriskit.trendSetter",
author: "Chris Kitch",
description: "A child SmartApp for Trend Setter for handling a group of devices.",
category: "My Apps",
iconUrl: "https://cdn.rawgit.com/Kriskit/SmartThingsPublic/master/smartapps/kriskit/trendsetter/icon.png",
iconX2Url: "https://cdn.rawgit.com/Kriskit/SmartThingsPublic/master/smartapps/kriskit/trendsetter/icon@2x.png",
iconX3Url: "https://cdn.rawgit.com/Kriskit/SmartThingsPublic/master/smartapps/kriskit/trendsetter/icon@3x.png",
parent: "kriskit.trendsetter:Trend Setter")
def version() {
return "1.0"
}
def typeDefinitions() {
return [
[
type: "switch",
singular: "Switch",
plural: "Switches",
deviceType: "Switch Group Device",
attributes: [
[name: "switch"]
]
],
[
type: "switchLevel",
singular: "Dimmer",
plural: "Dimmers",
deviceType: "Dimmer Group Device",
inherits: "switch",
attributes: [
[name: "level"]
]
],
[
type: "colorControl",
singular: "Colorful Light",
plural: "Colorful Lights",
deviceType: "Colorful Light Group Device",
inherits: "switchLevel",
attributes: [
[name: "hue"],
[name: "saturation"],
[name: "color"]
]
],
[
type: "powerMeter",
singular: "Power Meter",
plural: "Power Meters",
deviceType: "Power Meter Group Device",
attributes: [
[name: "power"]
]
]
]
}
// Setup
preferences {
page(name: "configure")
}
def configure() {
atomicState.typeDefinitions = null
def controller = getControllerDevice();
dynamicPage(name: "configure", uninstall: controller != null, install: true) {
if (!controller) {
section {
input "deviceType", "enum", title: "Device Type", required: true, submitOnChange: true, options: getDeviceTypeOptions()
paragraph "This cannot be changed once the group is created.", color: "#ffcc00"
}
}
if (deviceType) {
def definition = getTypeDefinition(deviceType)
section(title: controller == null ? "Grouping" : null) {
label title: "Group Name", required: true
input "devices", "capability.${deviceType}", title: "${definition.plural}", multiple: true, required: true, submitOnChange: controller != null
if (selectedDevicesContainsController()) {
paragraph "WARNING: You have selected the controller ${definition.singular.toLowerCase()} for this group. This will likely cause unexpected behaviour.\n\nPlease uncheck the '${controller.displayName}' from the selected ${definition.plural.toLowerCase()}.",
image: "https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-512.png"
}
}
if (controller == null) {
section(title: "Controller") {
input "deviceName", "text", title: "${definition.singular} Name", required: true, description: "For the controlling virtual ${definition.singular.toLowerCase()} to be created"
}
}
if (definition.advanced) {
section(title: "Advanced", hidden: true, hideable: true) {
}
}
}
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
installControllerDevice()
initialize()
}
def uninstalled() {
log.debug "Uninstalled"
}
def installControllerDevice() {
def definition = getTypeDefinition()
log.debug "Installing switch group controller device..."
addChildDevice("kriskit.trendSetter", definition.deviceType, UUID.randomUUID().toString(), null, ["name": deviceName, "label": deviceName, completedSetup: true])
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
def definition = getTypeDefinition()
addSubscriptions(definition)
def namesToCheck = definition.attributes?.collect { it.name }
updateControllerState(namesToCheck)
}
def addSubscriptions(definition) {
def controller = getControllerDevice()
definition.attributes?.each {
log.debug "Subscribing to ${it.name}..."
subscribe(devices, it.name, onDeviceAttributeChange)
}
}
// Subscription Handlers
def onDeviceAttributeChange(evt) {
def namesToCheck = atomicState.namesToCheck ?: []
log.debug "Device state change: ${evt.device.displayName} -> ${evt.name} = ${evt.value}"
if (!namesToCheck.any { it == evt.name })
namesToCheck.push(evt.name)
atomicState.namesToCheck = namesToCheck
runIn(1, "updateControllerState")
}
def updateControllerState() {
def namesToCheck = atomicState.namesToCheck
updateControllerState(namesToCheck)
atomicState.namesToCheck = null
}
def updateControllerState(namesToCheck) {
if (!namesToCheck)
return
def controller = getControllerDevice()
namesToCheck?.each { name ->
def values = devices?.currentValue(name)
values?.removeAll([null])
log.debug "Updating Controller State: $name -> $values"
controller.groupSync(name, values)
}
}
def performGroupCommand(command, arguments = null) {
runCommand(devices, command, arguments ?: [])
}
def runCommand(target, command, args) {
log.debug "Running command '${command}' with arguments ${args} on ${target}..."
$performCommand(target, command, args)
}
def getGroupCurrentValues(name) {
return devices?.currentValue(name)
}
// Utilities
def getTypeDefinitions() {
if (atomicState.version != version()) {
atomicState.typeDefinitions = null
atomicState.version = version()
}
if (atomicState.typeDefinitions)
return atomicState.typeDefinitions
log.debug "Building type definitions..."
def result = []
def definitions = typeDefinitions()
definitions?.each { definition ->
if (definition.inherits)
definition = mergeAttributes(definition, definitions.find { it.type == definition.inherits })
result.push(definition)
}
atomicState.typeDefinitions = result
return result
}
def mergeAttributes(definition, inheritedDefinition) {
inheritedDefinition.attributes?.each { attr ->
if (!definition.attributes?.any { it.name == attr.name })
definition.attributes.push(attr)
}
if (inheritedDefinition.inherits) {
def definitions = typeDefinitions()
definition = mergeAttributes(definition, definitions.find { it.type == inheritedDefinition.inherits })
}
return definition
}
def getControllerDevice() {
return getChildDevices()?.find { true }
}
def getTypeDefinition() {
return getTypeDefinition(deviceType)
}
def getTypeDefinition(type) {
return getTypeDefinitions().find {
it.type == type
}
}
def getDeviceTypeOptions() {
return getTypeDefinitions().collect {
["${it.type}": it.singular]
}
}
def selectedDevicesContainsController() {
def controller = getControllerDevice()
return devices?.any {
it?.deviceNetworkId == controller?.deviceNetworkId
}
}
private $performCommand(target, command, args) {
switch(args?.size()) {
default:
target?."$command"()
break
case 1:
target?."$command"(args[0])
break
case 2:
target?."$command"(args[0], args[1])
break
case 3:
target?."$command"(args[0], args[1], args[2])
break
case 4:
target?."$command"(args[0], args[1], args[2], args[3])
break
case 5:
target?."$command"(args[0], args[1], args[2], args[3], args[4], args[5])
break
case 6:
target?."$command"(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
break
case 7:
target?."$command"(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7])
break
case 8:
target?."$command"(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])
break
case 9:
target?."$command"(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9])
break
}
}

View File

@@ -1,58 +0,0 @@
/**
* Trend Setter
*
* Copyright 2015 Chris Kitch
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Trend Setter",
namespace: "kriskit.trendSetter",
author: "Chris Kitch",
description: "Uses virtual child devices to group other devices together and perform commands and aggregate data from the group.",
category: "My Apps",
iconUrl: "https://cdn.rawgit.com/Kriskit/SmartThingsPublic/master/smartapps/kriskit/trendsetter/icon.png",
iconX2Url: "https://cdn.rawgit.com/Kriskit/SmartThingsPublic/master/smartapps/kriskit/trendsetter/icon@2x.png",
iconX3Url: "https://cdn.rawgit.com/Kriskit/SmartThingsPublic/master/smartapps/kriskit/trendsetter/icon@3x.png",
singleInstance: true)
preferences {
page(name: "mainPage")
}
def mainPage() {
dynamicPage(name: "mainPage", title: "Your Groups", install: true, uninstall: true, submitOnChange: true) {
section {
app(name: "groups", appName: "Group", namespace: "kriskit.trendSetter", title: "Create Group...", multiple: true)
}
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// TODO: subscribe to attributes, devices, locations, etc.
}
def childUninstalled() {
}

View File

@@ -8,7 +8,7 @@
* 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
* 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
@@ -24,7 +24,7 @@ definition(
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
singleInstance: true
singleInstance: true
)
preferences {
@@ -37,10 +37,7 @@ preferences {
def mainPage() {
def bridges = bridgesDiscovered()
if (state.refreshUsernameNeeded) {
return bridgeLinking()
} else if (state.username && bridges) {
if (state.username && bridges) {
return bulbDiscovery()
} else {
return bridgeDiscovery()
@@ -105,22 +102,13 @@ def bridgeLinking() {
def nextPage = ""
def title = "Linking with your Hue"
def paragraphText
def paragraphText
if (selectedHue) {
if (state.refreshUsernameNeeded) {
paragraphText = "The current Hue username is invalid.\n\nPlease press the button on your Hue Bridge to re-link. "
} else {
paragraphText = "Press the button on your Hue Bridge to setup a link. "
}
} else {
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
}
paragraphText = "Press the button on your Hue Bridge to setup a link. "
} else {
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
}
if (state.username) { //if discovery worked
if (state.refreshUsernameNeeded) {
state.refreshUsernameNeeded = false
// Issue one poll with new username to cancel local polling with old username
poll()
}
nextPage = "bulbDiscovery"
title = "Success!"
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
@@ -198,7 +186,7 @@ void ssdpSubscribe() {
private sendDeveloperReq() {
def token = app.id
def host = getBridgeIP()
def host = getBridgeIP()
sendHubCommand(new physicalgraph.device.HubAction([
method: "POST",
path: "/api",
@@ -209,7 +197,7 @@ private sendDeveloperReq() {
}
private discoverHueBulbs() {
def host = getBridgeIP()
def host = getBridgeIP()
sendHubCommand(new physicalgraph.device.HubAction([
method: "GET",
path: "/api/${state.username}/lights",
@@ -231,8 +219,8 @@ private verifyHueBridge(String deviceNetworkId, String host) {
private verifyHueBridges() {
def devices = getHueBridges().findAll { it?.value?.verified != true }
devices.each {
def ip = convertHexToIP(it.value.networkAddress)
def port = convertHexToInt(it.value.deviceAddress)
def ip = convertHexToIP(it.value.networkAddress)
def port = convertHexToInt(it.value.deviceAddress)
verifyHueBridge("${it.value.mac}", (ip + ":" + port))
}
}
@@ -261,7 +249,7 @@ Map bulbsDiscovered() {
bulbs.each {
def value = "${it.name}"
def key = app.id +"/"+ it.id
logg += "$value - $key, "
logg += "$value - $key, "
bulbmap["${key}"] = value
}
}
@@ -288,23 +276,22 @@ def installed() {
def updated() {
log.trace "Updated with settings: ${settings}"
unsubscribe()
unschedule()
unschedule()
initialize()
}
def initialize() {
log.debug "Initializing"
unsubscribe(bridge)
state.inBulbDiscovery = false
state.bridgeRefreshCount = 0
state.bulbRefreshCount = 0
unsubscribe(bridge)
state.inBulbDiscovery = false
state.bridgeRefreshCount = 0
state.bulbRefreshCount = 0
state.updating = false
setupDeviceWatch()
if (selectedHue) {
addBridge()
addBulbs()
doDeviceSync()
runEvery5Minutes("doDeviceSync")
addBridge()
addBulbs()
doDeviceSync()
runEvery5Minutes("doDeviceSync")
}
}
@@ -322,14 +309,6 @@ def uninstalled(){
state.username = null
}
private setupDeviceWatch() {
def hub = location.hubs[0]
// Make sure that all child devices are enrolled in device watch
getChildDevices().each {
it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}")
}
}
private upgradeDeviceType(device, newHueType) {
def deviceType = getDeviceType(newHueType)
@@ -378,6 +357,7 @@ def addBulbs() {
if (d) {
log.debug "created ${d.displayName} with id $dni"
d.completedSetup = true
d.refresh()
}
} else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
@@ -407,23 +387,23 @@ def addBridge() {
if(vbridge) {
def d = getChildDevice(selectedHue)
if(!d) {
// compatibility with old devices
def newbridge = true
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
if (newDNI != it.deviceNetworkId) {
def oldDNI = it.deviceNetworkId
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
it.setDeviceNetworkId("${newDNI}")
// compatibility with old devices
def newbridge = true
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
if (newDNI != it.deviceNetworkId) {
def oldDNI = it.deviceNetworkId
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
it.setDeviceNetworkId("${newDNI}")
if (oldDNI == selectedHue) {
app.updateSetting("selectedHue", newDNI)
}
newbridge = false
}
}
}
if (newbridge) {
newbridge = false
}
}
}
if (newbridge) {
// Hue uses last 6 digits of MAC address as ID number, this number is shown on the bottom of the bridge
def idNumber = getBridgeIdNumber(selectedHue)
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["label": "Hue Bridge ($idNumber)"])
@@ -434,11 +414,9 @@ def addBridge() {
d.completedSetup = true
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
def childDevice = getChildDevice(d.deviceNetworkId)
childDevice?.sendEvent(name: "status", value: "Online")
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
updateBridgeStatus(childDevice)
childDevice.sendEvent(name: "idNumber", value: idNumber)
childDevice?.sendEvent(name: "idNumber", value: idNumber)
if (vbridge.value.ip && vbridge.value.port) {
if (vbridge.value.ip.contains(".")) {
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
@@ -590,47 +568,47 @@ void usernameHandler(physicalgraph.device.HubResponse hubResponse) {
@Deprecated
def locationHandler(evt) {
def description = evt.description
log.trace "Location: $description"
log.trace "Location: $description"
def hub = evt?.hubId
def parsedEvent = parseLanMessage(description)
parsedEvent << ["hub":hub]
if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:basic:1")) {
//SSDP DISCOVERY EVENTS
//SSDP DISCOVERY EVENTS
log.trace "SSDP DISCOVERY EVENTS"
def bridges = getHueBridges()
log.trace bridges.toString()
if (!(bridges."${parsedEvent.ssdpUSN.toString()}")) {
//bridge does not exist
//bridge does not exist
log.trace "Adding bridge ${parsedEvent.ssdpUSN}"
bridges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else {
// update the values
def ip = convertHexToIP(parsedEvent.networkAddress)
def port = convertHexToInt(parsedEvent.deviceAddress)
def host = ip + ":" + port
def ip = convertHexToIP(parsedEvent.networkAddress)
def port = convertHexToInt(parsedEvent.deviceAddress)
def host = ip + ":" + port
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
def dni = "${parsedEvent.mac}"
def d = getChildDevice(dni)
def networkAddress = null
if (!d) {
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
def d = getChildDevice(dni)
def networkAddress = null
if (!d) {
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
d = it
if (newDNI != it.deviceNetworkId) {
def oldDNI = it.deviceNetworkId
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
it.setDeviceNetworkId("${newDNI}")
if (newDNI != it.deviceNetworkId) {
def oldDNI = it.deviceNetworkId
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
it.setDeviceNetworkId("${newDNI}")
if (oldDNI == selectedHue) {
app.updateSetting("selectedHue", newDNI)
}
doDeviceSync()
}
}
}
doDeviceSync()
}
}
}
} else {
updateBridgeStatus(d)
if (d.getDeviceDataByName("networkAddress")) {
@@ -638,22 +616,22 @@ def locationHandler(evt) {
} else {
networkAddress = d.latestState('networkAddress').stringValue
}
log.trace "Host: $host - $networkAddress"
if(host != networkAddress) {
log.debug "Device's port or ip changed for device $d..."
dstate.ip = ip
dstate.port = port
dstate.name = "Philips hue ($ip)"
d.sendEvent(name:"networkAddress", value: host)
d.updateDataValue("networkAddress", host)
}
}
log.trace "Host: $host - $networkAddress"
if(host != networkAddress) {
log.debug "Device's port or ip changed for device $d..."
dstate.ip = ip
dstate.port = port
dstate.name = "Philips hue ($ip)"
d.sendEvent(name:"networkAddress", value: host)
d.updateDataValue("networkAddress", host)
}
}
}
} else if (parsedEvent.headers && parsedEvent.body) {
log.trace "HUE BRIDGE RESPONSES"
def headerString = parsedEvent.headers.toString()
if (headerString?.contains("xml")) {
log.trace "description.xml response (application/xml)"
log.trace "description.xml response (application/xml)"
def body = new XmlSlurper().parseText(parsedEvent.body)
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
def bridges = getHueBridges()
@@ -665,7 +643,7 @@ def locationHandler(evt) {
}
}
} else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) {
log.trace "description.xml response (application/json)"
log.trace "description.xml response (application/json)"
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
if (body.success != null) {
if (body.success[0] != null) {
@@ -702,7 +680,7 @@ def doDeviceSync(){
poll()
ssdpSubscribe()
discoverBridges()
checkBridgeStatus()
checkBridgeStatus()
}
/**
@@ -715,14 +693,10 @@ def doDeviceSync(){
private void updateBridgeStatus(childDevice) {
// Update activity timestamp if child device is a valid bridge
def vbridges = getVerifiedHueBridges()
def vbridge = vbridges.find {
"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()
}
def vbridge = vbridges.find {"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()}
vbridge?.value?.lastActivity = now()
if (vbridge && childDevice?.device?.currentValue("status") == "Offline") {
log.debug "$childDevice is back Online"
if(vbridge) {
childDevice?.sendEvent(name: "status", value: "Online")
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
}
}
@@ -731,37 +705,29 @@ private void updateBridgeStatus(childDevice) {
* for the bridge and all connected lights. Also, set ID number on bridge if not done previously.
*/
private void checkBridgeStatus() {
def bridges = getHueBridges()
// Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 11)
bridges.each {
def d = getChildDevice(it.value.mac)
if (d) {
// Set id number on bridge if not done
if (it.value.idNumber == null) {
it.value.idNumber = getBridgeIdNumber(it.value.serialNumber)
def bridges = getHueBridges()
// Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 11)
bridges.each {
def d = getChildDevice(it.value.mac)
if(d) {
// Set id number on bridge if not done
if (it.value.idNumber == null) {
it.value.idNumber = getBridgeIdNumber(it.value.serialNumber)
d.sendEvent(name: "idNumber", value: it.value.idNumber)
}
}
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
if (d.currentStatus == "Online") {
log.warn "$d is Offline"
d.sendEvent(name: "status", value: "Offline")
d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
log.warn "Bridge $it.value.idNumber is Offline"
d.sendEvent(name: "status", value: "Offline")
Calendar currentTime = Calendar.getInstance()
getChildDevices().each {
def id = getId(it)
if (state.bulbs[id]?.online == true) {
state.bulbs[id]?.online = false
state.bulbs[id]?.unreachableSince = currentTime.getTimeInMillis()
it.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
}
}
state.bulbs?.each {
it.value.online = false
}
} else if (d.currentStatus == "Offline") {
log.debug "$d is back Online"
d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
getChildDevices().each {
it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false)
}
} else {
d.sendEvent(name: "status", value: "Online")//setOnline(false)
}
}
@@ -813,24 +779,24 @@ def parse(childDevice, description) {
def parsedEvent = parseLanMessage(description)
if (parsedEvent.headers && parsedEvent.body) {
def headerString = parsedEvent.headers.toString()
def bodyString = parsedEvent.body.toString()
def bodyString = parsedEvent.body.toString()
if (headerString?.contains("json")) {
def body
try {
body = new groovy.json.JsonSlurper().parseText(bodyString)
} catch (all) {
log.warn "Parsing Body failed - trying again..."
poll()
}
if (body instanceof java.util.Map) {
// get (poll) reponse
return handlePoll(body)
def body
try {
body = new groovy.json.JsonSlurper().parseText(bodyString)
} catch (all) {
log.warn "Parsing Body failed - trying again..."
poll()
}
if (body instanceof java.util.Map) {
// get (poll) reponse
return handlePoll(body)
} else {
//put response
return handleCommandResponse(body)
}
}
} else {
}
}
} else {
log.debug "parse - got something other than headers,body..."
return []
}
@@ -851,19 +817,19 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
device.sendEvent([name: "colorTemperature", value: temp, descriptionText: "Color temperature has changed"])
// Return because color temperature change is not counted as a color change in SmartThings so no hex update necessary
return
}
}
if (hue != null) {
// 0-65535
def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int
events["hue"] = [name: "hue", value: value, descriptionText: "Color has changed", displayed: false]
}
}
if (sat != null) {
// 0-254
def value = Math.round(sat * 100 / 254) as int
events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false]
}
events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false]
}
// Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex
if (xy != null && colormode != "hs") {
@@ -949,15 +915,8 @@ private handleCommandResponse(body) {
updates[childDeviceNetworkId]."$eventType" = v
}
}
} else if (payload?.error) {
log.warn "Error returned from Hue bridge, error = ${payload?.error}"
// Check for unauthorized user
if (payload?.error?.type?.value == 1) {
log.error "Hue username is not valid"
state.refreshUsernameNeeded = true
state.username = null
}
return []
} else if (payload.error) {
log.warn "Error returned from Hue bridge error = ${body?.error}"
}
}
@@ -965,9 +924,12 @@ private handleCommandResponse(body) {
updates.each { childDeviceNetworkId, params ->
def device = getChildDevice(childDeviceNetworkId)
def id = getId(device)
sendBasicEvents(device, "on", params.on)
sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
// If device is offline, then don't send events which will update device watch
if (isOnline(id)) {
sendBasicEvents(device, "on", params.on)
sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
}
}
return []
}
@@ -1000,38 +962,39 @@ private handlePoll(body) {
def bulbs = getChildDevices()
for (bulb in body) {
def device = bulbs.find { it.deviceNetworkId == "${app.id}/${bulb.key}" }
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
if (device) {
if (bulb.value.state?.reachable) {
if (state.bulbs[bulb.key]?.online == false || state.bulbs[bulb.key]?.online == null) {
if (state.bulbs[bulb.key]?.online == false) {
// light just came back online, notify device watch
device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
def lastActivity = now()
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
log.debug "$device is Online"
}
// Mark light as "online"
state.bulbs[bulb.key]?.unreachableSince = null
state.bulbs[bulb.key]?.online = true
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
if (!state.updating) {
sendBasicEvents(device, "on", bulb.value?.state?.on)
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
}
} else {
if (state.bulbs[bulb.key]?.unreachableSince == null) {
// Store the first time where device was reported as "unreachable"
state.bulbs[bulb.key]?.unreachableSince = currentTime.getTimeInMillis()
}
if (state.bulbs[bulb.key]?.online || state.bulbs[bulb.key]?.online == null) {
} else if (state.bulbs[bulb.key]?.online) {
// Check if device was "unreachable" for more than 11 minutes and mark "offline" if necessary
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis() || state.bulbs[bulb.key]?.online == null) {
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis()) {
log.warn "$device went Offline"
state.bulbs[bulb.key]?.online = false
device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
}
}
log.warn "$device may not reachable by Hue bridge"
}
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
if (!state.updating) {
sendBasicEvents(device, "on", bulb.value?.state?.on)
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
}
}
}
return []
@@ -1050,16 +1013,16 @@ def updateHandler() {
def hubVerification(bodytext) {
log.trace "Bridge sent back description.xml for verification"
def body = new XmlSlurper().parseText(bodytext)
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
def bridges = getHueBridges()
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
if (bridge) {
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
} else {
log.error "/description.xml returned a bridge that didn't exist"
}
}
def body = new XmlSlurper().parseText(bodytext)
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
def bridges = getHueBridges()
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
if (bridge) {
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
} else {
log.error "/description.xml returned a bridge that didn't exist"
}
}
}
def on(childDevice) {
@@ -1068,7 +1031,7 @@ def on(childDevice) {
updateInProgress()
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [on: true])
return "Bulb is turning On"
return "Bulb is turning On"
}
def off(childDevice) {
@@ -1077,15 +1040,15 @@ def off(childDevice) {
updateInProgress()
createSwitchEvent(childDevice, "off")
put("lights/$id/state", [on: false])
return "Bulb is turning Off"
return "Bulb is turning Off"
}
def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'"
def id = getId(childDevice)
updateInProgress()
updateInProgress()
// 1 - 254
def level
def level
if (percent == 1)
level = 1
else
@@ -1119,7 +1082,7 @@ def setSaturation(childDevice, percent) {
def setHue(childDevice, percent) {
log.debug "Executing 'setHue($percent)'"
def id = getId(childDevice)
updateInProgress()
updateInProgress()
// 0 - 65535
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
// TODO should this be done by app only or should we default to on?
@@ -1131,7 +1094,7 @@ def setHue(childDevice, percent) {
def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'"
def id = getId(childDevice)
updateInProgress()
updateInProgress()
// 153 (6500K) to 500 (2000K)
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
createSwitchEvent(childDevice, "on")
@@ -1140,14 +1103,14 @@ def setColorTemperature(childDevice, huesettings) {
}
def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'"
log.debug "Executing 'setColor($huesettings)'"
def id = getId(childDevice)
updateInProgress()
updateInProgress()
def value = [:]
def hue = null
def sat = null
def xy = null
def value = [:]
def hue = null
def sat = null
def xy = null
// Prefer hue/sat over hex to make sure it works with the majority of the smartapps
if (huesettings.hue != null || huesettings.sat != null) {
@@ -1172,32 +1135,48 @@ def setColor(childDevice, huesettings) {
// value.xy = calculateXY(hex, model)
// Once groups, or scenes are introduced it might be a good idea to use unique models again
value.xy = calculateXY(hex)
}
}
*/
// Default behavior is to turn light on
value.on = true
// Default behavior is to turn light on
value.on = true
if (huesettings.level != null) {
if (huesettings.level <= 0)
value.on = false
else if (huesettings.level == 1)
value.bri = 1
else
if (huesettings.level != null) {
if (huesettings.level <= 0)
value.on = false
else if (huesettings.level == 1)
value.bri = 1
else
value.bri = Math.min(Math.round(huesettings.level * 254 / 100), 254)
}
value.alert = huesettings.alert ? huesettings.alert : "none"
value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4
}
value.alert = huesettings.alert ? huesettings.alert : "none"
value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4
// Make sure to turn off light if requested
if (huesettings.switch == "off")
value.on = false
// Make sure to turn off light if requested
if (huesettings.switch == "off")
value.on = false
createSwitchEvent(childDevice, value.on ? "on" : "off")
put("lights/$id/state", value)
put("lights/$id/state", value)
return "Setting color to $value"
}
def ping(childDevice) {
if (childDevice.device?.deviceNetworkId?.equalsIgnoreCase(selectedHue)) {
if (childDevice.device?.currentValue("status")?.equalsIgnoreCase("Online")) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Bridge is reachable", displayed: false, isStateChange: true)
return "Bridge is Online"
} else {
return "Bridge is Offline"
}
} else if (isOnline(getId(childDevice))) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
return "Device is Online"
} else {
return "Device is Offline"
}
}
private getId(childDevice) {
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
return childDevice.device?.deviceNetworkId[3..-1]
@@ -1248,30 +1227,30 @@ private getBridgeIdNumber(serialNumber) {
private getBridgeIP() {
def host = null
if (selectedHue) {
def d = getChildDevice(selectedHue)
if (d) {
if (d.getDeviceDataByName("networkAddress"))
host = d.getDeviceDataByName("networkAddress")
else
host = d.latestState('networkAddress').stringValue
}
if (host == null || host == "") {
def serialNumber = selectedHue
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
if (!bridge) {
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
}
if (bridge?.ip && bridge?.port) {
if (bridge?.ip.contains("."))
host = "${bridge?.ip}:${bridge?.port}"
else
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
} else if (bridge?.networkAddress && bridge?.deviceAddress)
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
}
log.trace "Bridge: $selectedHue - Host: $host"
}
return host
def d = getChildDevice(selectedHue)
if (d) {
if (d.getDeviceDataByName("networkAddress"))
host = d.getDeviceDataByName("networkAddress")
else
host = d.latestState('networkAddress').stringValue
}
if (host == null || host == "") {
def serialNumber = selectedHue
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
if (!bridge) {
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
}
if (bridge?.ip && bridge?.port) {
if (bridge?.ip.contains("."))
host = "${bridge?.ip}:${bridge?.port}"
else
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
} else if (bridge?.networkAddress && bridge?.deviceAddress)
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
}
log.trace "Bridge: $selectedHue - Host: $host"
}
return host
}
private Integer convertHexToInt(hex) {
@@ -1311,7 +1290,7 @@ private List getRealHubFirmwareVersions() {
* @param childDevice device to send event for
* @param setSwitch The new switch state, "on" or "off"
* @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since
* that should generate "off" instead of level change
* that should generate "off" instead of level change
*/
private void createSwitchEvent(childDevice, setSwitch, setLevel = null) {
@@ -1454,8 +1433,8 @@ private float[] calculateXY(colorStr, model = null) {
xy[0] = closestPoint.x;
xy[1] = closestPoint.y;
}
// xy[0] = PHHueHelper.precision(4, xy[0]);
// xy[1] = PHHueHelper.precision(4, xy[1]);
// xy[0] = PHHueHelper.precision(4, xy[0]);
// xy[1] = PHHueHelper.precision(4, xy[1]);
// TODO needed, assume it just sets number of decimals?
@@ -1473,7 +1452,7 @@ private float[] calculateXY(colorStr, model = null) {
*
* @param points the float array contain x and the y value. [x,y]
* @param model the model of the lamp, example: "LCT001" for hue bulb. Used to calculate the color gamut.
* If this value is empty the default gamut values are used.
* If this value is empty the default gamut values are used.
* @return the color value in hex (#ff03d3). If xy is null OR xy is not an array of size 2, Color. BLACK will be returned
*/
private String colorFromXY(points, model ) {
@@ -1787,4 +1766,3 @@ def hsvToHex(hue, sat, value = 100){
return "#$r1$g1$b1"
}

View File

@@ -242,6 +242,8 @@ def installed() {
} else {
initialize()
}
// Check for new devices and remove old ones every 3 hours
runEvery3Hours('updateDevices')
}
// called after settings are changed
@@ -269,19 +271,9 @@ private removeChildDevices(devices) {
def initialize() {
log.debug "initialize"
updateDevices()
// Check for new devices and remove old ones every 3 hours
runEvery5Minutes('updateDevices')
setupDeviceWatch()
}
// Misc
private setupDeviceWatch() {
def hub = location.hubs[0]
// Make sure that all child devices are enrolled in device watch
getChildDevices().each {
it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}")
}
}
Map apiRequestHeaders() {
return ["Authorization": "Bearer ${state.lifxAccessToken}",
@@ -384,7 +376,7 @@ def updateDevices() {
def data = [
label: device.label,
level: Math.round((device.brightness ?: 1) * 100),
switch: device.power,
switch: device.connected ? device.power : "unreachable",
colorTemperature: device.color.kelvin
]
if (device.product.capabilities.has_color) {
@@ -395,42 +387,18 @@ def updateDevices() {
} else {
childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data)
}
childDevice?.completedSetup = true
} else {
if (device.product.capabilities.has_color) {
sendEvent(name: "color", value: colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int))
sendEvent(name: "hue", value: device.color.hue / 3.6)
sendEvent(name: "saturation", value: device.color.saturation * 100)
}
childDevice.sendEvent(name: "label", value: device.label)
childDevice.sendEvent(name: "level", value: Math.round((device.brightness ?: 1) * 100))
childDevice.sendEvent(name: "switch.setLevel", value: Math.round((device.brightness ?: 1) * 100))
childDevice.sendEvent(name: "switch", value: device.power)
childDevice.sendEvent(name: "colorTemperature", value: device.color.kelvin)
childDevice.sendEvent(name: "model", value: device.product.name)
}
if (state.devices[device.id] == null) {
// State missing, add it and set it to opposite status as current status to provoke event below
state.devices[device.id] = [online : !device.connected]
}
if (!state.devices[device.id]?.online && device.connected) {
// Device came online after being offline
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false)
log.debug "$device is back Online"
} else if (state.devices[device.id]?.online && !device.connected) {
// Device went offline after being online
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false)
log.debug "$device went Offline"
}
state.devices[device.id] = [online: device.connected]
}
getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each {
log.info("Deleting ${it.deviceNetworkId}")
state.devices[it.deviceNetworkId] = null
deleteChildDevice(it.deviceNetworkId)
}
runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block
}
def refreshDevices() {
log.info("Refreshing all devices...")
getChildDevices().each { device ->
device.refresh()
}
}