Compare commits

..

1 Commits

Author SHA1 Message Date
Elnar Hajiyev
970e5404ac MSA-1277: I'd like to submit significant capability updates to 2 device handlers widely used in UK (and EU): Fibaro Dimmer 1 and Fibaro Dimmer 2. There are 2 main additions to the original code developed by SmartThings team:
1)Addition of "scene" attribute to enable dimmer devices to emit scene activations to SmartThings hub, which can be listened upon by and reacted to by smartapps. This is a significant capability, which was omitted in the existing device handlers because it allows the use of double and triple clicks, secondary switch single/double/triple clicks to perform additional actions. A good example, when in a bedroom normal clicks to turn on/off lights would control main bedroom lights, while double clicks or clicks of the secondary switch S2 could control small table lights which are connected to SmartThings hub. Other examples can be activating Good Night or Good Morning routines, etc.

2)I also added the full list of hardware parameters for both switches to be controllable directly in the SmartThings App UI - settings of the device. This allows users with no coding skills to modify hardware parameters of Fibaro Dimmers without having to fiddle with any code changes. This also makes it possible to have different configurations for different dimmers by using just one device handler, as opposed to creating multiple copies of it depending on the set of parameters that one needs to set.

In addition to extensions of the device handlers I am also submitting a demo smartapp that can be used to subscribe to scene activations of Fibaro Dimmers. Its a very simple app, but one that is necessary to be able to take advantage of newly added functionality of Fibaro Dimmers on SmartThings platform.
2016-05-12 02:52:47 -05:00
177 changed files with 7124 additions and 7550 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,8 +33,8 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
attributeState("inactive", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
attributeState("active", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
}
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
@@ -127,10 +127,9 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelR
def map = [ displayed: true ]
switch (cmd.sensorType) {
case 1:
def cmdScale = cmd.scale == 1 ? "F" : "C"
map.name = "temperature"
map.unit = getTemperatureScale()
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
map.name = "temperature"
map.unit = cmd.scale == 1 ? "F" : "C"
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, map.unit, cmd.precision)
break
case 3:
map.name = "illuminance"
@@ -279,4 +278,4 @@ private encap(physicalgraph.zwave.Command cmd) {
} else {
crc16(cmd)
}
}
}

View File

@@ -274,7 +274,6 @@ private Map makeTemperatureResult(value) {
name: 'temperature',
value: "" + value,
descriptionText: "${linkText} is ${value}°${temperatureScale}",
unit: temperatureScale
]
}

View File

@@ -254,8 +254,7 @@ private Map getTemperatureResult(value) {
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
descriptionText: descriptionText
]
}

View File

@@ -94,11 +94,11 @@ def parse(String description) {
def cmd = zwave.parse(description, [0x31: 1, 0x32: 1, 0x60: 3])
if (cmd) {
result = createEvent(zwaveEvent(cmd))
log.debug "Parse returned ${result?.descriptionText}"
storeGraphData(result.name, result.value)
} else {
log.debug "zwave.parse returned null command. Cannot create event"
}
log.debug "Parse returned ${result?.descriptionText}"
storeGraphData(result.name, result.value)
return result
}

View File

@@ -28,7 +28,6 @@ metadata {
attribute "powerSupply", "enum", ["USB Cable", "Battery"]
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A,0x5A"
}
simulator {
@@ -353,7 +352,7 @@ def configure() {
motionSensitivity == "minimum" ? 0 : 64)
//5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: (8*60)) //association group 1
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: 8*60) //association group 1
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2

View File

@@ -21,8 +21,6 @@ metadata {
capability "Sensor"
capability "Battery"
command "configureAfterSecure"
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
}

View File

@@ -87,27 +87,16 @@ def beep() {
up to this long from the time you send the message to the time you hear a sound.
*/
// Used source endpoint of 0x02 because we are using smartthings manufacturer specific cluster.
[
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"raw 0xFC05 {15 0A 11 00 00 15 01}"
]
}

View File

@@ -47,9 +47,6 @@ metadata {
command "everywhereJoin"
command "everywhereLeave"
command "forceOff"
command "forceOn"
}
/**
@@ -67,10 +64,8 @@ metadata {
}
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "on", label: '${name}', action: "forceOff", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
state "off", label: '${name}', action: "forceOn", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821"
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff"
state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821"
}
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
state "station1", label:'${currentValue}', action:"preset1"
@@ -143,22 +138,8 @@ metadata {
* one place.
*
*/
def off() {
if (device.currentState("switch")?.value == "on") {
onAction("off")
}
}
def forceOff() {
onAction("off")
}
def on() {
if (device.currentState("switch")?.value == "off") {
onAction("on")
}
}
def forceOn() {
onAction("on")
}
def off() { onAction("off") }
def on() { onAction("on") }
def volup() { onAction("volup") }
def voldown() { onAction("voldown") }
def preset1() { onAction("1") }
@@ -257,11 +238,11 @@ def onAction(String user, data=null) {
def actions = null
switch (user) {
case "on":
boseSetPowerState(true)
actions = boseSetPowerState(true)
break
case "off":
boseSetNowPlaying(null, "STANDBY")
boseSetPowerState(false)
actions = boseSetPowerState(false)
break
case "volume":
actions = boseSetVolume(data)
@@ -766,16 +747,8 @@ def cb_boseSetInput(xml, input) {
*/
def boseSetPowerState(boolean enable) {
log.info "boseSetPowerState(${enable})"
// Fix to get faster update of power status back from speaker after sending on/off
// Instead of queuing the command to be sent after the refresh send it directly via sendHubCommand
// Note: This is a temporary hack that should be replaced by a re-design of the
// DTH to use sendHubCommand for all commands
sendHubCommand(bosePOST("/key", "<key state=\"press\" sender=\"Gabbo\">POWER</key>"))
sendHubCommand(bosePOST("/key", "<key state=\"release\" sender=\"Gabbo\">POWER</key>"))
sendHubCommand(boseGET("/now_playing"))
if (enable) {
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", 5)
}
queueCallback('nowPlaying', "cb_boseSetPowerState", enable ? "POWERON" : "POWEROFF")
return boseRefreshNowPlaying()
}
/**
@@ -814,11 +787,10 @@ def cb_boseSetPowerState(xml, state) {
*/
def cb_boseConfirmPowerOn(xml, tries) {
def result = []
def attempt = tries as Integer
log.warn "boseConfirmPowerOn() attempt #$attempt"
if (xml.attributes()['source'] == "STANDBY" && attempt > 0) {
log.warn "boseConfirmPowerOn() attempt #" + tries
if (xml.attributes()['source'] == "STANDBY" && tries > 0) {
result << boseRefreshNowPlaying()
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", attempt-1)
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", tries-1)
}
return result
}

View File

@@ -0,0 +1,146 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* CentraLite Dimmer
*
* Author: SmartThings
* Date: 2013-12-04
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "CentraLite Dimmer", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Switch"
capability "Power Meter"
capability "Configuration"
capability "Refresh"
capability "Sensor"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
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"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("power", key: "SECONDARY_CONTROL") {
attributeState "power", label:'${currentValue} W'
}
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch","refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "Parse description $description"
def name = null
def value = null
if (description?.startsWith("catchall:")) {
def msg = zigbee.parse(description)
log.trace msg
log.trace "data: $msg.data"
} else if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.debug "Read attr: $description"
if (descMap.cluster == "0006" && descMap.attrId == "0000") {
name = "switch"
value = descMap.value.endsWith("01") ? "on" : "off"
} else {
def reportValue = description.split(",").find {it.split(":")[0].trim() == "value"}?.split(":")[1].trim()
name = "power"
// assume 16 bit signed for encoding and power divisor is 10
value = Integer.parseInt(reportValue, 16) / 10
}
} else if (description?.startsWith("on/off:")) {
log.debug "Switch command"
name = "switch"
value = description?.endsWith(" 1") ? "on" : "off"
}
def result = createEvent(name: name, value: value)
log.debug "Parse returned ${result?.descriptionText}"
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
// Commands to device
def on() {
'zcl on-off on'
}
def off() {
'zcl on-off off'
}
def setLevel(value) {
log.trace "setLevel($value)"
sendEvent(name: "level", value: value)
def level = hexString(Math.round(value * 255/100))
def cmd = "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 2000}"
log.debug cmd
cmd
}
def meter() {
"st rattr 0x${device.deviceNetworkId} 1 0xB04 0x50B"
}
def refresh() {
"st rattr 0x${device.deviceNetworkId} 1 0xB04 0x50B"
}
def configure() {
[
"zdo bind 0x${device.deviceNetworkId} 1 1 8 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 0xB04 {${device.zigbeeId}} {}"
]
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}

View File

@@ -89,17 +89,14 @@ def parse(String description) {
log.debug "TEMP"
map.name = "temperature"
map.value = getTemperature(descMap.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
log.debug "COOLING SETPOINT"
map.name = "coolingSetpoint"
map.value = getTemperature(descMap.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
log.debug "HEATING SETPOINT"
map.name = "heatingSetpoint"
map.value = getTemperature(descMap.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
log.debug "MODE"
map.name = "thermostatMode"
@@ -172,7 +169,7 @@ def setHeatingSetpoint(degrees) {
def degreesInteger = Math.round(degrees)
log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "heatingSetpoint", "value": degreesInteger, "unit": temperatureScale)
sendEvent("name": "heatingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}"
@@ -183,7 +180,7 @@ def setCoolingSetpoint(degrees) {
if (degrees != null) {
def degreesInteger = Math.round(degrees)
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "coolingSetpoint", "value": degreesInteger, "unit": temperatureScale)
sendEvent("name": "coolingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
}

View File

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

View File

@@ -1,36 +0,0 @@
# Connected Cree LED Bulb
Cloud Execution
Works with:
* [Connected Cree LED Bulb](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Polling** - represents that poll() can be implemented for the device
* **Refresh** - _refresh()_ command for status updates
* **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
Connected Cree LED Bulb with cloud polling it every __5min__
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* __12min__ checkInterval
## 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:
* [Cree Connected LED Bulb Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)

View File

@@ -14,17 +14,18 @@
*
*/
//@Deprecated: Moved to ZLL Dimmer Bulb
metadata {
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Configuration"
capability "Polling"
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: "0000,0019"
}
// simulator metadata
@@ -85,27 +86,15 @@ def setLevel(value) {
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.levelRefresh()
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh()
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
}
def healthPoll() {
log.debug "healthPoll()"
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh()
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
def poll() {
zigbee.onOffRefresh() + zigbee.levelRefresh()
}
def configure() {
unschedule()
runEvery5Minutes("healthPoll")
// Device-Watch allows 2 check-in misses from device + ping
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
zigbee.onOffRefresh() + zigbee.levelRefresh()
log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
}

View File

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

View File

@@ -1,47 +0,0 @@
# Z-wave Dimmer Switch
Local Execution on V2 Hubs
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.
* __32min__ checkInterval
## 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,11 +20,8 @@ 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"
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "Z-Wave Plug-In Dimmer"
fingerprint inClusters: "0x26"
}
simulator {
@@ -45,10 +42,6 @@ metadata {
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
}
preferences {
input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
}
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
@@ -77,30 +70,11 @@ metadata {
}
main(["switch"])
details(["switch", "level", "refresh"])
details(["switch", "level", "indicator", "refresh"])
}
}
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()
break
case "off":
indicatorWhenOff()
break
case "never":
indicatorNever()
break
default:
indicatorWhenOn()
break
}
}
def parse(String description) {
def result = null
if (description != "updated") {
@@ -164,7 +138,6 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
log.debug "productTypeId: ${cmd.productTypeId}"
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
updateDataValue("MSR", msr)
updateDataValue("manufacturer", cmd.manufacturerName)
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}
@@ -218,13 +191,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 = []
@@ -235,19 +201,19 @@ def refresh() {
delayBetween(commands,100)
}
void indicatorWhenOn() {
sendEvent(name: "indicatorStatus", value: "when on", display: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
def indicatorWhenOn() {
sendEvent(name: "indicatorStatus", value: "when on")
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
}
void indicatorWhenOff() {
sendEvent(name: "indicatorStatus", value: "when off", display: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
def indicatorWhenOff() {
sendEvent(name: "indicatorStatus", value: "when off")
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
}
void indicatorNever() {
sendEvent(name: "indicatorStatus", value: "never", display: false)
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
def indicatorNever() {
sendEvent(name: "indicatorStatus", value: "never")
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
}
def invertSwitch(invert=true) {
@@ -257,4 +223,4 @@ def invertSwitch(invert=true) {
else {
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
}
}
}

View File

@@ -23,7 +23,6 @@ metadata {
capability "Sensor"
capability "Refresh"
capability "Relative Humidity Measurement"
capability "Health Check"
command "generateEvent"
command "raiseSetpoint"
@@ -32,14 +31,13 @@ metadata {
command "switchMode"
command "switchFanMode"
attribute "thermostatSetpoint", "number"
attribute "thermostatStatus", "string"
attribute "thermostatSetpoint","number"
attribute "thermostatStatus","string"
attribute "maxHeatingSetpoint", "number"
attribute "minHeatingSetpoint", "number"
attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "string"
attribute "deviceAlive", "enum", ["true", "false"]
attribute "deviceTemperatureUnit", "number"
}
tiles {
@@ -122,21 +120,6 @@ metadata {
}
void installed() {
// The device refreshes every 5 minutes by default so if we miss 2 refreshes we can consider it offline
// Using 12 minutes because in testing, device health team found that there could be "jitter"
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "cloud"], displayed: false)
}
// Device Watch will ping the device to proactively determine if the device has gone offline
// If the device was online the last time we refreshed, trigger another refresh as part of the ping.
def ping() {
def isAlive = device.currentValue("deviceAlive") == "true" ? true : false
if (isAlive) {
refresh()
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
@@ -165,13 +148,15 @@ 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]
event << [value: sendValue, 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()
event << [value: sendValue, unit: temperatureScale, displayed: false]
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
event << [value: sendValue, displayed: false]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false]
@@ -181,11 +166,7 @@ def generateEvent(Map results) {
} else if (name=="humidity") {
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
} else if (name == "deviceAlive") {
isChange = isStateChange(device, name, value.toString())
event['isStateChange'] = isChange
event['displayed'] = false
} else {
} else {
isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed]
@@ -253,9 +234,9 @@ void setHeatingSetpoint(setpoint) {
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
generateSetpointEvent()
generateStatusEvent()
@@ -272,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) {
@@ -289,9 +271,9 @@ void setCoolingSetpoint(setpoint) {
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
generateSetpointEvent()
generateStatusEvent()
@@ -301,17 +283,18 @@ 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()
if (parent.resumeProgram(deviceId)) {
if (parent.resumeProgram(this, deviceId)) {
sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false)
runIn(5, "poll")
log.debug "resumeProgram() is done"
sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true)
} else {
sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false)
log.error "Error resumeProgram() check parent.resumeProgram(deviceId)"
log.error "Error resumeProgram() check parent.resumeProgram(this, deviceId)"
}
}
@@ -371,6 +354,7 @@ def switchFanMode() {
}
def switchToFanMode(nextMode) {
log.debug "switching to fan mode: $nextMode"
def returnCommand
@@ -422,7 +406,7 @@ def generateOperatingStateEvent(operatingState) {
def off() {
log.debug "off"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("off", deviceId))
if (parent.setMode (this,"off", deviceId))
generateModeEvent("off")
else {
log.debug "Error setting new mode."
@@ -436,7 +420,7 @@ def off() {
def heat() {
log.debug "heat"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("heat", deviceId))
if (parent.setMode (this,"heat", deviceId))
generateModeEvent("heat")
else {
log.debug "Error setting new mode."
@@ -454,7 +438,7 @@ def emergencyHeat() {
def auxHeatOnly() {
log.debug "auxHeatOnly"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("auxHeatOnly", deviceId))
if (parent.setMode (this,"auxHeatOnly", deviceId))
generateModeEvent("auxHeatOnly")
else {
log.debug "Error setting new mode."
@@ -468,7 +452,7 @@ def auxHeatOnly() {
def cool() {
log.debug "cool"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("cool", deviceId))
if (parent.setMode (this,"cool", deviceId))
generateModeEvent("cool")
else {
log.debug "Error setting new mode."
@@ -482,7 +466,7 @@ def cool() {
def auto() {
log.debug "auto"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("auto", deviceId))
if (parent.setMode (this,"auto", deviceId))
generateModeEvent("auto")
else {
log.debug "Error setting new mode."
@@ -505,7 +489,7 @@ def fanOn() {
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode)
} else {
log.debug "Error setting new mode."
@@ -526,7 +510,7 @@ def fanAuto() {
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode)
} else {
log.debug "Error setting new mode."
@@ -536,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)
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint )
}
else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint)
} 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)
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint)
}
}
void raiseSetpoint() {
@@ -594,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) {
@@ -627,7 +608,7 @@ void raiseSetpoint() {
targetvalue = maxCoolingSetpoint
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false)
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
log.info "In mode $mode raiseSetpoint() to $targetvalue"
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
@@ -641,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) {
@@ -672,7 +644,7 @@ void lowerSetpoint() {
targetvalue = minCoolingSetpoint
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false)
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
log.info "In mode $mode lowerSetpoint() to $targetvalue"
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
@@ -681,83 +653,66 @@ void lowerSetpoint() {
//called by raiseSetpoint() and lowerSetpoint()
void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow alterSetpoint"
} else {
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
def targetHeatingSetpoint
def targetCoolingSetpoint
def targetHeatingSetpoint
def targetCoolingSetpoint
def temperatureScaleHasChanged = false
if (location.temperatureScale == "C") {
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){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
if ( heatingSetpoint < 40.0 || coolingSetpoint < 40.0 ) {
temperatureScaleHasChanged = true
}
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = coolingSetpoint
}
//step1: check thermostatMode, enforce limits before sending request to cloud
if (mode == "heat" || mode == "auxHeatOnly"){
if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = coolingSetpoint
}
} else if (mode == "cool") {
//enforce limits before sending request to cloud
if (temp.value < heatingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = heatingSetpoint
targetCoolingSetpoint = temp.value
}
}
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else if (mode == "cool") {
//enforce limits before sending request to cloud
if (temp.value < heatingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
log.error "Error alterSetpoint()"
if (mode == "heat" || mode == "auxHeatOnly"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
}
targetHeatingSetpoint = heatingSetpoint
targetCoolingSetpoint = temp.value
}
if ( temperatureScaleHasChanged )
generateSetpointEvent()
generateStatusEvent()
}
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else {
log.error "Error alterSetpoint()"
if (mode == "heat" || mode == "auxHeatOnly"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
}
}
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}"
@@ -767,25 +722,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)
}
@@ -799,7 +765,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

@@ -0,0 +1,456 @@
/**
* Device Type Definition File
*
* Device Type: Fibaro Dimmer
* File Name: fibaro-dimmer.groovy
* Initial Release: 2015-06-00
* Author: SmartThings
*
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
// Automatically generated. Make future change here.
definition (name: "Fibaro Dimmer 1", namespace: "smartthings", author: "SmartThings, Elnar Hajiyev") {
capability "Switch Level"
capability "Actuator"
capability "Switch"
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Configuration"
//Extending Fibaro Dimmer 1 devices with scene attribute
attribute "scene", "number"
command "configureParams"
fingerprint deviceId: "0x1101", inClusters: "0x72,0x86,0x70,0x85,0x8E,0x26,0x7A,0x27,0x73,0xEF,0x26,0x2B"
}
simulator {
status "on": "command: 2003, payload: FF"
status "off": "command: 2003, payload: 00"
status "09%": "command: 2003, payload: 09"
status "10%": "command: 2003, payload: 0A"
status "33%": "command: 2003, payload: 21"
status "66%": "command: 2003, payload: 42"
status "99%": "command: 2003, payload: 63"
// reply messages
reply "2001FF,delay 5000,2602": "command: 2603, payload: FF"
reply "200100,delay 5000,2602": "command: 2603, payload: 00"
reply "200119,delay 5000,2602": "command: 2603, payload: 19"
reply "200132,delay 5000,2602": "command: 2603, payload: 32"
reply "20014B,delay 5000,2602": "command: 2603, payload: 4B"
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
}
tiles {
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"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configureParams", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "configure", label:'', action:"configureParams", icon:"st.secondary.configure"
}
main(["switch"])
details(["switch", "refresh",
"levelSliderControl",
"configureParams"
])
}
preferences {
input name: "param1", type: "enum", defaultValue: "255", required: true,
options: ["0" : "0",
"1" : "1",
"2" : "2",
"255" : "255"],
title: "1. Activate / deactivate functions ALL ON / ALL OFF.\n" +
"Available settings:\n" +
"0 = All ON not active, All OFF not active,\n" +
"1 = All ON not active, All OFF active,\n" +
"2 = All ON active, All OFF not active,\n" +
"255 = All ON active, All OFF active.\n" +
"Default value: 255."
input name: "param6", type: "number", range: "0..2", defaultValue: "0", required: true,
title: "6. Sending commands to control devices assigned to 1st association group (key no. 1).\n" +
"Available settings:\n" +
"0 = commands are sent when device is turned on and off.\n" +
"1 = commands are sent when device is turned off. Enabling device does not send control commands. Double-clicking key sends 'turn on' command, dimmers memorize the last saved state (e.g. 50% brightness).\n" +
"2 = commands are sent when device is turned off. Enabling device does not send control commands. Double-clicking key sends 'turn on' command and dimmers are set to 100% brightness.\n" +
"Default value: 1.\n\n" +
"NOTE: Parameter 15 value must be set to 1 to work properly. This activates the double-click functionality - dimmer/roller shutter control."
input name: "param7", type: "number", range: "0..1", defaultValue: "1", required: true,
title: "7. Checking the device status before sending a control frame from the key no. 2.\n" +
"Available settings:\n" +
"0 = Device status is not checked.\n" +
"1 = Device status is checked.\n" +
"Default value: 1.\n\n" +
"Info: Key no. 2 is not represented by any physical device expect of devices on association list. " +
"This functionality prevents of lack of reaction on pressing key no. 2 through polling devices from list one by one and checking thier actual states.\n" +
"It is not possible to check the device status before sending a control frame from the key no. 2 if roller blind switch is chosen in parameter 14 (value 2)\n" +
"If devices state is checked before sending asociation then parameter 19 should be set to value 0."
input name: "param8", type: "number", range: "1..99", defaultValue: "1", required: true,
title: "8. The percentage of a dimming step at automatic control.\n" +
"Available settings: 1-99\n" +
"Default value: 1."
input name: "param9", type: "number", range: "1..255", defaultValue: "5", required: true,
title: "9. Time of manually moving the Dimmer between the extreme dimming values.\n" +
"Available settings: 1-255 (10ms 2.5s)\n" +
"Default value: 5."
input name: "param10", type: "number", range: "0..255", defaultValue: "1", required: true,
title: "10. Time of Automatic moving the Dimmer between the extreme dimming values.\n" +
"Available settings: 0-255 (0ms 2.5s)\n" +
"0 - this value disables the smooth change in light intensity\n" +
"Default value: 1.\n\n" +
"NOTE value 0 is required for inductive and capacitive devices unsuitable for dimming, (e.g. fluorescent lamps , motors etc.)."
input name: "param11", type: "number", range: "1..99", defaultValue: "1", required: true,
title: "11. The percentage of a dimming step at manual control.\n" +
"Available settings: 1-99\n" +
"Default value: 1."
input name: "param12", type: "number", range: "2..99", defaultValue: "99", required: true,
title: "12. Maximum Dimmer level control.\n" +
"Available settings: 2-99\n" +
"Default value: 99."
input name: "param13", type: "number", range: "1..98", defaultValue: "2", required: true,
title: "13. Minimum Dimmer level control\n" +
"Available settings: 1-98\n" +
"Default value: 2.\n\n" +
"NOTE: The maximum level may not be lower than the minimum level.\n" +
"Recommended values of parameters 12 and 13 (max and min level) for controlling the devices are as follows:\n" +
"- AC motors [min 60%, max 99%]\n" +
"- fluorescent lamps, fluorescent tubes, LED [min 98%, max 99%] [parameter 10 set to 0]"
input name: "param14", type: "number", range: "0..2", defaultValue: "0", required: true,
title: "14. Switch type. Choose between momentary switch and toggle switch.\n" +
"Available settings:\n" +
"0 = momentary switch,\n" +
"1 = toggle switch,\n" +
"2 = Roller blind switch (UP / DOWN) - two switch keys operate the Dimmer.\n" +
"Default value: 0."
input name: "param15", type: "number", range: "0..1", defaultValue: "1", required: true,
title: "15. Double click option (set lightning at 100%).\n" +
"Available settings:\n" +
"0 = Double click disabled,\n" +
"1 = Double click enabled.\n" +
"Default value: 1."
input name: "param16", type: "number", range: "0..1", defaultValue: "1", required: true,
title: "16. Saving the state of the device after a power failure. The Dimmer will return to the last position before power failure.\n" +
"Available settings:\n" +
"0 = the Dimmer does not save the state after a power failure, it returns to 'off' position,\n" +
"1 = the Dimmer saves its state before power failure.\n" +
"Default value: 1."
input name: "param17", type: "number", range: "0..1", defaultValue: "0", required: true,
title: "17. The function of 3 - way switch, provides the option to double key no. 1. " +
"The Dimmer may control two toggle push-buttons or an infinite number of momentary push-buttons.\n" +
"Available settings:\n" +
"0 = the function of 3-way switch is disabled,\n" +
"1 = the function of 3-way switch is enabled.\n" +
"Default value: 0."
input name: "param18", type: "number", range: "0..1", defaultValue: "0", required: true,
title: "18. The function of synchronizing the light level for associated devices. The Dimmer communicates the position to the associated device.\n" +
"Available settings:\n" +
"0 = function disabled,\n" +
"1 = function enabled.\n" +
"Default value: 0."
input name: "param19", type: "number", range: "0..1", defaultValue: "0", required: true,
title: "19. Assigns bistable key status to the device status.\n" +
"Available settings:\n" +
"0 = [On / Off] device changes status on key status change.\n" +
"1 = Device status depends on key status: ON when the key is ON, OFF when the key is OFF.\n"
"Default value: 0.\n\n" +
"Info: Remote control from Fibaro System Is Still Possible. This function is useful When you want display status of external devices, e.g. Motion Sensor, in Fibaro System."
input name: "param30", type: "number", range: "0..3", defaultValue: "3", required: true,
title: "30. Alarm of any type (general alarm, water flooding alarm, smoke alarm: CO, CO2, temperature alarm).\n" +
"Available settings:\n" +
"0 = DEACTIVATION - the device does not respond to alarm data frames,\n" +
"1 = ALARM DIMMER ON - the device turns on after detecting an alarm,\n" +
"2 = ALARM DIMMER OFF - the device turns off after detecting an alarm,\n" +
"3 = ALARM FLASHING the device periodically changes its status to the opposite, when it detects an alarm within 10 min."
"Default value: 3."
input name: "param39", type: "number", range: "1..65535", defaultValue: "600", required: true,
title: "39. Active flashing alarm time.\n" +
"Available settings: [1-65535][ms]\n" +
"Default value: 600."
input name: "param41", type: "number", range: "0..1", defaultValue: "0", required: true,
title: "41. Scene activation functionality.\n" +
"The device offers the possibility of sending commands compatible with Command class scene activation. " +
"Information is sent to devices assigned to association group no. 3. " +
"Controllers such as Home Center 2 are able to interpret such commands and based on these commands they activate scenes, to which specific scene IDs have been assigned. " +
"The user may expand the functionality of the button connected to inputs S1 and S2 by distinguishing the actions of keys connected to those inputs. " +
"For example: double click would activate the scene “goodnight” and triple click would activate the scene “morning”.\n" +
"Available settings:\n" +
"0 = functionality deactivated,\n" +
"1 = functionality activated.\n" +
"Default value: 0.\n\n" +
"Scene ID is determined as follows:\n" +
"Momentary switch:\n" +
"Input S1: holding down ID 12 (option inactive in case of roller blind switch), " +
"releasing ID 13, double click ID 14 (depends on parameters 15 value - 1 = double click active), " +
"triple click ID 15, one click ID 16.\n" +
"Input S2: holding down ID 22 (option inactive in case of roller blind switch), " +
"releasing ID 23, double click ID 24 (depends on parameters 15 value - 1 = double click active) option inactive in case of roller blind switch, " +
"triple click ID 25, one click ID 26.\n\n" +
"Toggle switch:\n" +
"Input S1: holding down ID 12, releasing ID 13, double click ID 14 (depends on parameters 15 value - 1 = double click active), triple click ID 15. " +
"If parameter no. 19 is set to 0: single click ID16 is sent. If parameter no. 19 is set to 1 following IDs are sent: switch from “off” to “on” ID 10, switch from “on” to “off” ID 11.\n" +
"Input S2: holding down ID 22, releasing ID 23, double click ID 24, (depends on parameters 15 value - 1 = double click active), triple click ID 25. " +
"If parameter no.19 set to 0 (default), then one click ID 26 is sent. If parameter no.19 is set to 1 following IDs are sent: switch from “off” to “on” ID 20, switch from “on” to “off” ID 21.\n\n" +
"Roller blind switch:\n" +
"Input S1, Turning on the light: switch from “off” to “on” ID 10, double click ID 14 (depends of parameter 15 value 1 - double click functionality), " +
"triple click ID 15, brighten ID 17, releasing ID 13.\n" +
"Input S2, Turning off the light: switch from “on” to “off” ID 11, triple click ID 25, dim ID 18, releasing ID 13."
input name: "param20", type: "number", range: "101..170", defaultValue: "110", required: true,
title: "ADVANCED FUNCTION\n\n" +
"20. The function enabling the change of control impulse length. " +
"This function will enable decreasing the minimum level of the Dimmer by extending the control impulse. " +
"By changing the minimum level, the user may completely dim LED bulbs. Not all LED bulbs available on the market have the dimming option.\n" +
"Available settings:\n" +
"Default values: 110 for 50Hz (UK), 101 for 60Hz (USA).\n\n"+
"WARNING!\nWrong setting of the function may cause incorrect operation of the Dimmer."
input name: "paramAssociationGroup1", type: "bool", defaultValue: false, required: true,
title: "The Dimmer 1 provides the association of three groups.\n\n" +
"1 = 1st group is assigned to key no. 1,\n" +
"Default value: true"
input name: "paramAssociationGroup2", type: "bool", defaultValue: false, required: true,
title: "2nd group is assigned to key no. 2\n" +
"Default value: true"
input name: "paramAssociationGroup3", type: "bool", defaultValue: true, required: true,
title: "3rd group for controllers such Home Center for state reporting,\n" +
"Default value: false"
}
}
def parse(String description) {
def item1 = [
canBeCurrentState: false,
linkText: getLinkText(device),
isStateChange: false,
displayed: false,
descriptionText: description,
value: description
]
def result
def cmd = zwave.parse(description, [0x26: 1, 0x70: 2, 072: 2])
//log.debug "cmd: ${cmd}"
if (cmd) {
result = createEvent(cmd, item1)
}
else {
item1.displayed = displayed(description, item1.isStateChange)
result = [item1]
}
if(result?.descriptionText)
log.debug "Parse returned ${result?.descriptionText}"
result
}
def createEvent(physicalgraph.zwave.commands.sceneactivationv1.SceneActivationSet cmd, Map map) {
log.debug( "Scene ID: $cmd.sceneId")
log.debug( "Dimming Duration: $cmd.dimmingDuration")
sendEvent(name: "scene", value: "$cmd.sceneId", data: [switchType: "$settings.param20"], descriptionText: "Scene id $cmd.sceneId was activated", isStateChange: true)
log.debug( "Scene id $cmd.sceneId was activated" )
}
def createEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, Map item1) {
def result = doCreateEvent(cmd, item1)
for (int i = 0; i < result.size(); i++) {
result[i].type = "physical"
}
result
}
def createEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd, Map item1) {
def result = doCreateEvent(cmd, item1)
for (int i = 0; i < result.size(); i++) {
result[i].type = "physical"
}
result
}
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStartLevelChange cmd, Map item1) {
[]
}
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd, Map item1) {
[response(zwave.basicV1.basicGet())]
}
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd, Map item1) {
def result = doCreateEvent(cmd, item1)
for (int i = 0; i < result.size(); i++) {
result[i].type = "physical"
}
result
}
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd, Map item1) {
def result = doCreateEvent(cmd, item1)
result[0].descriptionText = "${item1.linkText} is ${item1.value}"
result[0].handlerName = cmd.value ? "statusOn" : "statusOff"
for (int i = 0; i < result.size(); i++) {
result[i].type = "digital"
}
result
}
def doCreateEvent(physicalgraph.zwave.Command cmd, Map item1) {
def result = [item1]
item1.name = "switch"
item1.value = cmd.value ? "on" : "off"
item1.handlerName = item1.value
item1.descriptionText = "${item1.linkText} was turned ${item1.value}"
item1.canBeCurrentState = true
item1.isStateChange = isStateChange(device, item1.name, item1.value)
item1.displayed = item1.isStateChange
if (cmd.value >= 5) {
def item2 = new LinkedHashMap(item1)
item2.name = "level"
item2.value = cmd.value as String
item2.unit = "%"
item2.descriptionText = "${item1.linkText} dimmed ${item2.value} %"
item2.canBeCurrentState = true
item2.isStateChange = isStateChange(device, item2.name, item2.value)
item2.displayed = false
result << item2
}
result
}
def createEvent(physicalgraph.zwave.Command cmd, Map map) {
// Handles any Z-Wave commands we aren't interested in
log.debug "UNHANDLED COMMAND $cmd"
}
def on() {
log.info "on"
delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
}
def off() {
delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
}
def setLevel(value) {
def level = Math.min(value as Integer, 99)
delayBetween ([zwave.basicV1.basicSet(value: level).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
}
def setLevel(value, duration) {
def level = Math.min(value as Integer, 99)
def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format()
}
def poll() {
zwave.switchMultilevelV1.switchMultilevelGet().format()
}
def refresh() {
zwave.switchMultilevelV1.switchMultilevelGet().format()
}
def createEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd, Map item1) {
log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'"
}
def configureParams() {
log.debug "Configuring ${device.displayName} parameters"
def cmds = []
cmds << zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1, scaledConfigurationValue: param1.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 6, size: 1, scaledConfigurationValue: param6.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 7, size: 1, scaledConfigurationValue: param7.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 8, size: 1, scaledConfigurationValue: param8.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 9, size: 1, scaledConfigurationValue: param9.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 10, size: 1, scaledConfigurationValue: param10.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 11, size: 1, scaledConfigurationValue: param11.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 13, size: 1, scaledConfigurationValue: param13.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 15, size: 1, scaledConfigurationValue: param15.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 16, size: 1, scaledConfigurationValue: param16.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 19, size: 1, scaledConfigurationValue: param19.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 20, size: 1, scaledConfigurationValue: param20.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 30, size: 1, scaledConfigurationValue: param30.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 39, size: 1, scaledConfigurationValue: param39.toInteger()).format()
cmds << zwave.configurationV1.configurationSet(parameterNumber: 41, size: 1, scaledConfigurationValue: param41.toInteger()).format()
// Register for Group 1
if(paramAssociationGroup1) {
cmds << zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId]).format()
}
else {
cmds << zwave.associationV2.associationRemove(groupingIdentifier:1, nodeId: [zwaveHubNodeId]).format()
}
// Register for Group 2
if(paramAssociationGroup2) {
cmds << zwave.associationV2.associationSet(groupingIdentifier:2, nodeId: [zwaveHubNodeId]).format()
}
else {
cmds << zwave.associationV2.associationRemove(groupingIdentifier:2, nodeId: [zwaveHubNodeId]).format()
}
// Register for Group 3
if(paramAssociationGroup3) {
cmds << zwave.associationV2.associationSet(groupingIdentifier:3, nodeId: [zwaveHubNodeId]).format()
}
else {
cmds << zwave.associationV2.associationRemove(groupingIdentifier:3, nodeId: [zwaveHubNodeId]).format()
}
delayBetween(cmds, 500)
}
def updated() {
log.debug "updated()"
response(["delay 2000"] + configureParams() + refresh())
}

View File

@@ -0,0 +1,787 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Fibaro Dimmer 2", namespace: "smartthings", author: "Rajiv, Elnar Hajiyev") {
capability "Energy Meter"
capability "Actuator"
capability "Switch"
capability "Power Meter"
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Configuration"
capability "Switch Level"
//Extending Fibaro Dimmer 2 devices with scene attribute
attribute "scene", "number"
command "reset"
command "configureAfterSecure"
fingerprint deviceId: "0x1001", inClusters: "0x5E, 0x20, 0x86, 0x72, 0x26, 0x5A, 0x59, 0x85, 0x73, 0x98, 0x7A, 0x56, 0x70, 0x31, 0x32, 0x8E, 0x60, 0x75, 0x71, 0x27, 0x22, 0xEF, 0x2B"
}
// simulator metadata
simulator {
status "on": "command: 2003, payload: FF"
status "off": "command: 2003, payload: 00"
status "09%": "command: 2003, payload: 09"
status "10%": "command: 2003, payload: 0A"
status "33%": "command: 2003, payload: 21"
status "66%": "command: 2003, payload: 42"
status "99%": "command: 2003, payload: 63"
for (int i = 0; i <= 10000; i += 1000) {
status "power ${i} W": new physicalgraph.zwave.Zwave().meterV3.meterReport(
scaledMeterValue: i, precision: 3, meterType: 4, scale: 2, size: 4).incomingMessage()
}
for (int i = 0; i <= 100; i += 10) {
status "energy ${i} kWh": new physicalgraph.zwave.Zwave().meterV3.meterReport(
scaledMeterValue: i, precision: 3, meterType: 0, scale: 0, size: 4).incomingMessage()
}
["FF", "00", "09", "0A", "21", "42", "63"].each { val ->
reply "2001$val,delay 100,2602": "command: 2603, payload: $val"
}
}
// tile definitions
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"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
}
valueTile("power", "device.power", decoration: "flat", width: 2, height: 2) {
state "default", label:'${currentValue} W'
}
valueTile("energy", "device.energy", decoration: "flat", width: 2, height: 2) {
state "default", label:'${currentValue} kWh'
}
standardTile("reset", "device.energy", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'reset kWh', action:"reset"
}
standardTile("configureAfterSecure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "configure", label:'', action:"configureAfterSecure", icon:"st.secondary.configure"
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch","power","energy"])
details(["switch","power","energy","configureAfterSecure","refresh","reset"])
}
preferences {
def paragraph = "GROUP 0 - The Dimmer 2 behavior - Basic functionalities"
input name: "param1", type: "number", range: "1..98", defaultValue: "1", required: true,
title: paragraph + "\n\n" +
"1. Minimum brightness level. " +
"This parameter is set automatically during the calibration process. " +
"The parameter can be changed manually after the calibration.\n" +
"Available settings: 1-98 - percentage level of brightness.\n" +
"Default value: 1."
input name: "param2", type: "number", range: "2..99", defaultValue: "99", required: true,
title: "2. Maximum brightness level. " +
"This parameter is set automatically during the calibration process. " +
"The parameter can be changed manually after the calibration.\n" +
"Available settings: 2-99 - percentage level of brightness.\n" +
"Default value: 99."
input name: "param3", type: "number", range: "1..99", defaultValue: "1", required: true,
title: "3. Incandescence level of dimmable compact fluorescent lamps. " +
"Virtual value set as a percentage level between parameters MIN (1%) and MAX (99%). " +
"The Dimmer 2 will set to this value after first switch on. " +
"It is required for warming up and switching dimmable compact fluorescent lamps and certain types of light sources.\n" +
"Available settings: 1-99 - percentage level of brightness.\n" +
"Default value: 1."
input name: "param4", type: "number", range: "0..255", defaultValue: "0", required: true,
title: "4. Incandescence time of dimmable compact fluorescent lamps. " +
"This parameter determines the time required for switching compact fluorescent lamps and certain types of light sources. " +
"Setting this parameter to 0 will disable the incandescence functionality.\n" +
"Available settings: 0-255 (0-25.5s).\n" +
"Default value: 0."
input name: "param5", type: "number", range: "1..99", defaultValue: "1", required: true,
title: "5. Automatic control - dimming step size. " +
"This parameter defines the percentage value of dimming step during the automatic control.\n" +
"Available settings: 1-99 - dimming step percentage value.\n" +
"Default value: 1."
input name: "param6", type: "number", range: "0..255", defaultValue: "1", required: true,
title: "6. Automatic control - time of a dimming step. " +
"This parameter defines the time of single dimming step set in parameter 5 during the automatic control.\n" +
"Available settings: 0-255 (0-2.55s, in 10ms steps).\n" +
"Default value: 1."
input name: "param7", type: "number", range: "1..99", defaultValue: "1", required: true,
title: "7. Manual control - dimming step size. " +
"This parameter defines the percentage value of dimming step during the manual control.\n" +
"Available settings: 1-99 - dimming step percentage value.\n" +
"Default value: 1."
input name: "param8", type: "number", range: "0..255", defaultValue: "5", required: true,
title: "8. Manual control - time of a dimming step. " +
"This parameter defines the time of single dimming step set in parameter 7 during the manual control.\n" +
"Available settings: 0-255 (0-2.55s, in 10ms steps).\n" +
"Default value: 5."
input name: "param9", type: "number", range: "0..1", defaultValue: "1", required: true,
title: "9. State of the device after a power failure. " +
"The Dimmer 2 will return to the last state before power failure.\n" +
"Available settings:\n" +
"0 = the Dimmer 2 does not save the state before a power failure, it returns to „off” position,\n" +
"1 = the Dimmer 2 restores its state before power failure.\n" +
"Default value: 1."
input name: "param10", type: "number", range: "0..32767", defaultValue: "0", required: true,
title: "10. Timer functionality (auto - off). " +
"This parameter allows to automatically switch off the device after specified time from switching on the light source. " +
"It may be useful when the Dimmer 2 is installed in the stairway.\n" +
"Available settings: 0 - Function disabled,\n1-32767 - time to turn off measured in seconds (1s-9.1h).\n" +
"Default value: 0."
input name: "param11", type: "enum", defaultValue: "255", required: true,
options: ["0" : "0",
"1" : "1",
"2" : "2",
"255" : "255"],
title: "11. ALL ON/ALL OFF function. " +
"Parameter allows for activation/deactivation of Z-Wave commands enabling/disabling all devices located in direct range of the main controller.\n" +
"Available settings:\n" +
"0 = All ON not active, All OFF not active,\n" +
"1 = All ON not active, All OFF active,\n" +
"2 = All ON active, All OFF not active,\n" +
"255 = All ON active, All OFF active.\n" +
"Default value: 255."
input name: "param13", type: "number", range: "0..2", defaultValue: "0", required: true,
title: "13. Force auto-calibration. " +
"Changing value of this parameter will force the calibration process. " +
"During the calibration parameter is set to 1 or 2 and switched to 0 upon completion.\n" +
"Available settings:\n" +
"0 = readout,\n" +
"1 = force auto-calibration of the load without FIBARO Bypass 2,\n" +
"2 = force auto-calibration of the load with FIBARO Bypass 2.\n" +
"Default value: 0."
input name: "param15", type: "number", range: "0..99", defaultValue: "30", required: true,
title: "15. Burnt out bulb detection. " +
"Function based on the sudden power variation of a specific value, interpreted as a LOAD ERROR.\n" +
"Available settings:\n0 - function disabled,\n" +
"1-99 - percentage value of power variation, compared to standard power consumption, measured during the calibration procedure (to be interpreted as load error/burnt out bulb).\n" +
"Default value: 30."
input name: "param16", type: "number", range: "0..255", defaultValue: "5", required: true,
title: "16. Time delay of a burnt out bulb (parameter 15) or overload (parameter 39) detection. " +
"Time of delay (in seconds) for power variation detection, interpreted as a LOAD ERROR or OVERLOAD detection (too much power connected to the Dimmer 2).\n" +
"Available settings:\n0 - detection of a burnt out bulb disabled,\n1-255 - delay time in seconds.\n" +
"Default value: 5."
input name: "param19", type: "number", range: "0..99", defaultValue: "0", required: true,
title: "19. Forced switch on brightness level. " +
"If the parameter is active, switching on the Dimmer 2 (S1 single click) will always set this brightness level.\n" +
"Available settings:\n0 - function disabled,\n1-99 - percentage level of brightness.\n" +
"Default value: 0."
paragraph = "GROUP 20 - Dimmer 2 operation - Switches"
input name: "param20", type: "number", range: "0..2", defaultValue: "0", required: true,
title: paragraph + "\n\n" +
"20. Switch type. " +
"Choose between momentary, toggle and roller blind switch.\n" +
"Available settings:\n" +
"0 = momentary switch,\n1 = toggle switch,\n2 = roller blind switch - two switches operate the Dimmer 2 (S1 to brighten, S2 to dim).\n" +
"Default value: 0."
input name: "param21", type: "number", range: "0..1", defaultValue: "0", required: true,
title: "21. The value sent to associated devices on single click. " +
"This parameter defines the value sent to devices associated with Dimmer 2 after its enabling.\n" +
"Available settings:\n" +
"0 = 0xFF value is sent, which will set associated devices to their last saved state,\n" +
"1 = current Dimmer 2 state is sent, which will synchronize brightness level of associated devices (other dimmers for example).\n" +
"Default value: 0."
input name: "param22", type: "number", range: "0..1", defaultValue: "0", required: true,
title: "22. Assign toggle switch status to the device status. " +
"By default each change of toggle switch position results in action of Dimmer 2 (switch on/off) regardless the physical connection of contacts.\n" +
"Available settings:\n" +
"0 = device changes status on switch status change,\n1 = device status is synchronized with switch status.\n" +
"Default value: 0."
input name: "param23", type: "number", range: "0..1", defaultValue: "1", required: true,
title: "23. Double click option - set the brightness level to MAX.\n" +
"Available settings:\n" +
"0 = double click disabled,\n" +
"1 = double click enabled.\n" +
"Default value: 1."
input name: "param24", type: "enum", defaultValue: "0", required: true,
options: ["0" : "0",
"1" : "1",
"2" : "2",
"4" : "4",
"8" : "8",
"16" : "16"],
title: "24. Command frames sent in 2nd and 3rd association groups (S1 associations). " +
"Parameter determines, which actions will not result in sending frames to association groups.\n" +
"Available settings:\n" +
"0 = all actions send to association groups,\n" +
"1 = do not send when switching ON (single click),\n" +
"2 = do not send when switching OFF (single click),\n" +
"4 = do not send when changing dimming level (holding and releasing),\n" +
"8 = do not send on double click,\n" +
"16 = send 0xFF value on double click.\n" +
"Default value: 0."
input name: "param25", type: "enum", defaultValue: "0", required: true,
options: ["0" : "0",
"1" : "1",
"2" : "2",
"4" : "4",
"8" : "8",
"16" : "16"],
title: "25. Command frames sent in 4th and 5th association groups (S2 associations). " +
"Parameter determines, which actions will not result in sending frames to association groups.\n" +
"Available settings:\n" +
"0 = all actions send to association groups,\n" +
"1 = do not send when switching ON (single click),\n" +
"2 = do not send when switching OFF (single click),\n" +
"4 = do not send when changing dimming level (holding and releasing),\n" +
"8 = do not send on double click,\n" +
"16 = send 0xFF value on double click.\n" +
"Default value: 0."
input name: "param26", type: "number", range: "0..1", defaultValue: "0", required: true,
title: "26. The function of 3-way switch. " +
"Switch no. 2 controls the Dimmer 2 additionally (in 3-way switch mode). Function disabled for parameter 20 set to 2 (roller blind switch).\n" +
"Available settings:\n" +
"0 = 3-way switch function for S2 disabled,\n" +
"1 = 3-way switch function for S2 enabled.\n" +
"Default value: 0."
input name: "param27", type: "enum", defaultValue: "15", required: true,
options: ["0" : "0",
"1" : "1",
"2" : "2",
"4" : "4",
"8" : "8",
"15" : "15"],
title: "27. Associations in Z-Wave network security mode. " +
"This parameter defines how commands are sent in speci ed association groups: as secure or non-secure. " +
"Parameter is active only in Z-Wave network security mode. It does not apply to 1st lifeline group.\n" +
"Available settings:\n" +
"0 = all groups (II-V) sent as non-secure,\n" +
"1 = 2nd group sent as secure,\n" +
"2 = 3rd group sent as secure,\n" +
"4 = 4th group sent as secure,\n" +
"8 = 5th group sent as secure,\n" +
"15 = all groups (II-V) sent as secure.\n" +
"Default value: 15."
input name: "param28", type: "number", range: "0..1", defaultValue: "0", required: true,
title: "28. Scene activation functionality.\n" +
"Available settings:\n" +
"0 = functionality deactivated,\n" +
"1 = functionality activated.\n" +
"Default value: 0.\n\n" +
"SCENE ID depends on the switch type configurations.\n" +
"Momentary switches:\n" +
"SCENE ID: S1 input:\n\t16 : 1 x click\n\t14 : 2 x click\n\t-- : 3 x click\n\t12 : hold\n\t13 : release\n" +
"SCENE ID: S2 input:\n\t26 : 1 x click\n\t24 : 2 x click\n\t25 : 3 x click\n\t22 : hold\n\t23 : release\n\n" +
"Toggle switches:\n" +
"SCENE ID: S1 input:\n\t10 : OFF to ON\n\t11 : ON to OFF\n\t14 : 2 x click\n\t-- : 3 x click\n" +
"SCENE ID: S2 input:\n\t20 : OFF to ON\n\t21 : ON to OFF\n\t24 : 2 x click\n\t25 : 3 x click\n\n" +
"Roller blinds switches:\n" +
"SCENE ID: S1 input:\n\t10 : turn ON (1 x click)\n\t13 : release\n\t14 : 2 x click\n\t-- : 3 x click\n\t17 : brightening\n" +
"SCENE ID: S2 input:\n\t11 : turn OFF (1 x click)\n\t13 : release\n\t14 : 2 x click\n\t15 : 3 x click\n\t18 : dimming"
input name: "param29", type: "number", range: "0..1", defaultValue: "0", required: true,
title: "29. Switch functionality of S1 and S2. " +
"This parameter allows for switching the role of keys connected to S1 and S2 without changes in connection.\n" +
"Available settings:\n" +
"0 = standard mode,\n" +
"1 = S1 operates as S2, S2 operates as S1.\n" +
"Default value: 0."
paragraph = "GROUP 30 - Dimmer 2 operation - Advanced functionality"
input name: "param30", type: "number", range: "0..2", defaultValue: "2", required: true,
title: paragraph + "\n\n" +
"30. Load control mode. " +
"This parameter allows to set the desired load control mode. " +
"The de- vice automatically adjusts correct control mode, but the installer may force its change using this parameter. " +
"Forced auto-calibration will set this parameters value to 2.\n" +
"Available settings:\n" +
"0 = forced leading edge control,\n" +
"1 = forced trailing edge control,\n" +
"2 = control mode selected automatically (based on auto-calibration).\n" +
"Default value: 2."
input name: "param32", type: "number", range: "0..2", defaultValue: "2", required: true,
title: "32. On/Off mode. " +
"This mode is necessary while connecting non-dimmable light sources. " +
"Setting this parameter to 1 automatically ignores brightening/dimming time settings. " +
"Forced auto-calibration will set this parameters value to 2.\n" +
"Available settings:\n" +
"0 = on/off mode disabled (dimming is possible),\n" +
"1 = on/off mode enabled (dimming is not possible),\n" +
"2 = mode selected automatically.\n" +
"Default value: 2."
input name: "param34", type: "number", range: "0..2", defaultValue: "1", required: true,
title: "34. Soft-Start functionality. " +
"Time required to warm up the lament of halogen bulb.\n" +
"Available settings:\n" +
"0 = no soft-start,\n" +
"1 = short soft-start (0.1s),\n" +
"2 = long soft-start (0.5s).\n" +
"Default value: 1."
input name: "param35", type: "number", range: "0..4", defaultValue: "1", required: true,
title: "35. Auto-calibration after power on. " +
"This parameter determines the trigger of auto-calibration procedure, e.g. power on, load error, etc.\n" +
"Available settings:\n" +
"0 = No auto-calibration of the load after power on,\n" +
"1 = Auto-calibration performed after first power on,\n" +
"2 = Auto-calibration performed after each power on,\n" +
"3 = Auto-calibration performed after first power on or after each LOAD ERROR alarm (no load, load failure, burnt out bulb), if parameter 37 is set to 1 also after alarms: SURGE (Dimmer 2 output overvoltage) and OVERCURRENT (Dimmer 2 output overcurrent),\n" +
"4 = Auto-calibration performed after each power on or after each LOAD ERROR alarm (no load, load failure, burnt out bulb), if parameter 37 is set to 1 also after alarms: SURGE (Dimmer 2 output overvoltage) and OVERCURRENT (Dimmer 2 output overcurrent).\n" +
"Default value: 1."
input name: "param37", type: "number", range: "0..1", defaultValue: "1", required: true,
title: "37. Behaviour of the Dimmer 2 after OVERCURRENT or SURGE. " +
"Occuring of errors related to surge or overcurrent results in turning off the output to prevent possible malfunction. " +
"By default the device performs three attempts to turn on the load (useful in case of momentary, short failures of the power supply).\n" +
"Available settings:\n" +
"0 = device permanently disabled until re-ena- bling by command or external switch,\n" +
"1 = three attempts to turn on the load.\n" +
"Default value: 1."
input name: "param39", type: "number", range: "0..350", defaultValue: "250", required: true,
title: "39. Power limit - OVERLOAD. " +
"Reaching the defined value will result in turning off the load. " +
"Additional apparent power limit of 350VA is active by default.\n" +
"Available settings:\n0 - functionality disabled,\n1-350 - 1-350W.\n" +
"Default value: 250."
paragraph = "GROUP 40 - Dimmer 2 operation - Alarms"
input name: "param40", type: "number", range: "0..3", defaultValue: "3", required: true,
title: paragraph + "\n\n" +
"40. Response to General Purpose Alarm.\n" +
"Available settings:\n" +
"0 = No reaction,\n" +
"1 = Turn on the load,\n" +
"2 = Turn off the load,\n" +
"3 = Load blinking.\n" +
"Default value: 3."
input name: "param41", type: "number", range: "0..3", defaultValue: "2", required: true,
title: "41. Response to Water Flooding Alarm.\n" +
"Available settings:\n" +
"0 = No reaction,\n" +
"1 = Turn on the load,\n" +
"2 = Turn off the load,\n" +
"3 = Load blinking.\n" +
"Default value: 2."
input name: "param42", type: "number", range: "0..3", defaultValue: "3", required: true,
title: "42. Response to Smoke, CO or CO2 Alarm.\n" +
"Available settings:\n" +
"0 = No reaction,\n" +
"1 = Turn on the load,\n" +
"2 = Turn off the load,\n" +
"3 = Load blinking.\n" +
"Default value: 3."
input name: "param43", type: "number", range: "0..3", defaultValue: "1", required: true,
title: "43. Response to Temperature Alarm.\n" +
"Available settings:\n" +
"0 = No reaction,\n" +
"1 = Turn on the load,\n" +
"2 = Turn off the load,\n" +
"3 = Load blinking.\n" +
"Default value: 1."
input name: "param44", type: "number", range: "1..32767", defaultValue: "600", required: true,
title: "44. Time of alarm state.\n" +
"Available settings: 1-32767 (1-32767 seconds).\n" +
"Default value: 600."
paragraph = "Alarm settings - reports"
input name: "param45", type: "number", range: "0..1", defaultValue: "1", required: true,
title: paragraph + "\n\n" +
"45. OVERLOAD alarm report (load power consumption too high).\n" +
"Available settings:\n" +
"0 = No reaction,\n" +
"1 = Send an alarm frame.\n" +
"Default value: 1."
input name: "param46", type: "number", range: "0..1", defaultValue: "1", required: true,
title: "46. LOAD ERROR alarm report (no load, load failure, burnt out bulb).\n" +
"Available settings:\n" +
"0 = No reaction,\n" +
"1 = Send an alarm frame.\n" +
"Default value: 1."
input name: "param47", type: "number", range: "0..1", defaultValue: "1", required: true,
title: "47. OVERCURRENT alarm report (short circuit, burnt out bulb causing overcurrent).\n" +
"Available settings:\n" +
"0 = No reaction,\n" +
"1 = Send an alarm frame.\n" +
"Default value: 1."
input name: "param48", type: "number", range: "0..1", defaultValue: "1", required: true,
title: "48. SURGE alarm report (Dimmer 2 output overvoltage).\n" +
"Available settings:\n" +
"0 = No reaction,\n" +
"1 = Send an alarm frame.\n" +
"Default value: 1."
input name: "param49", type: "number", range: "0..1", defaultValue: "1", required: true,
title: "49. OVERHEAT (critical temperature) and VOLTAGE DROP (low voltage) alarm report.\n" +
"Available settings:\n" +
"0 = No reaction,\n" +
"1 = Send an alarm frame.\n" +
"Default value: 1."
paragraph = "GROUP 50 - Active power and energy reports"
input name: "param50", type: "number", range: "0..100", defaultValue: "10", required: true,
title: paragraph + "\n\n" +
"50. Active power reports. " +
"The parameter defines the power level change that will result in a new power report being sent. " +
"The value is a percentage of the previous report.\n" +
"Available settings:\n0 - power reports disabled,\n1-100 (1-100%) - power report threshold.\n" +
"Default value: 10."
input name: "param52", type: "number", range: "0..32767", defaultValue: "3600", required: true,
title: "52. Periodic active power and energy reports. " +
"Parameter 52 defines a time period between consecutive reports. Timer is reset and counted from zero after each report.\n" +
"Available settings:\n0 - periodic reports disabled,\n1-32767 (1-32767 seconds).\n" +
"Default value: 3600."
input name: "param53", type: "number", range: "0..255", defaultValue: "10", required: true,
title: "53. Energy reports. " +
"Energy level change which will result in sending a new energy report.\n" +
"Available settings:\n0 - energy reports disabled,\n1-255 (0.01-2.55 kWh) - report triggering threshold.\n" +
"Default value: 10."
input name: "param54", type: "number", range: "0..1", defaultValue: "0", required: true,
title: "54. Self-measurement. " +
"The Dimmer 2 may include active power and energy consumed by itself in reports sent to the main controller.\n" +
"Available settings:\n" +
"0 = Self-measurement inactive,\n" +
"1 = Self-measurement active.\n" +
"Default value: 0."
input name: "param58", type: "number", range: "0..2", defaultValue: "0", required: true,
title: "58. Method of calculating the active power. This parameter defines how to calculate active power. " +
"It is useful in a case of 2-wire connection with light sources other than resistive.\n" +
"Available settings:\n" +
"0 = measurement based on the standard algorithm,\n" +
"1 = approximation based on the calibration data,\n" +
"2 = approximation based on the control angle.\n" +
"Default value: 0."
input name: "param59", type: "number", range: "0..500", defaultValue: "0", required: true,
title: "59. Approximated power at the maximum brightness level. " +
"This parameter determines the approximate value of the power that will be reported by the device at its maximum brightness level.\n" +
"Available settings: 0-500 (0-500W) - power consumed by the load at the maximum brightness level.\n" +
"Default value: 0."
input name: "paramAssociationGroup1", type: "bool", defaultValue: true, required: true,
title: "The Dimmer 2 provides the association of five groups.\n\n" +
"1st Association Group „Lifeline”,\n" +
"Default value: true"
input name: "paramAssociationGroup2", type: "bool", defaultValue: true, required: true,
title: "2nd Association Group „On/Off (S1)”,\n" +
"Default value: true"
input name: "paramAssociationGroup3", type: "bool", defaultValue: false, required: true,
title: "3rd Association Group „Dimmer (S1)”,\n" +
"Default value: false"
input name: "paramAssociationGroup4", type: "bool", defaultValue: false, required: true,
title: "4th Association Group „On/Off (S2)”,\n" +
"Default value: false"
input name: "paramAssociationGroup5", type: "bool", defaultValue: false, required: true,
title: "5th Association Group „Dimmer (S2)”.\n" +
"Default value: false"
}
}
def parse(String description) {
log.trace(description)
log.debug("RAW command: $description")
def result = null
if (description != "updated") {
def cmd = zwave.parse(description.replace("98C1", "9881"), [0x20: 1, 0x26: 3, 0x32: 3, 0x25: 1, 0x98: 1, 0x70: 1, 0x85: 2, 0x9B: 1, 0x90: 1, 0x73: 1, 0x30: 1, 0x28: 1, 0x72: 1])
if (cmd) {
result = zwaveEvent(cmd)
}
}
log.debug "Parsed '${description}' to ${result.inspect()}"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.sceneactivationv1.SceneActivationSet cmd) {
log.debug( "Scene ID: $cmd.sceneId")
log.debug( "Dimming Duration: $cmd.dimmingDuration")
sendEvent(name: "scene", value: "$cmd.sceneId", data: [switchType: "$settings.param20"], descriptionText: "Scene id $cmd.sceneId was activated", isStateChange: true)
log.debug( "Scene id $cmd.sceneId was activated" )
}
// Devices that support the Security command class can send messages in an encrypted form;
// they arrive wrapped in a SecurityMessageEncapsulation command and must be unencapsulated
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
log.trace(cmd)
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x26: 3, 0x32: 3, 0x25: 1, 0x98: 1, 0x70: 1, 0x85: 2, 0x9B: 1, 0x90: 1, 0x73: 1, 0x30: 1, 0x28: 1, 0x72: 1]) // can specify command class versions here like in zwave.parse
if (encapsulatedCommand) {
return zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
createEvent(descriptionText: cmd.toString())
}
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
log.trace(cmd)
dimmerEvents(cmd)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
log.trace(cmd)
//dimmerEvents(cmd)
}
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelReport cmd) {
log.trace(cmd)
dimmerEvents(cmd)
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd)
{
log.trace(cmd)
dimmerEvents(cmd)
}
def dimmerEvents(physicalgraph.zwave.Command cmd) {
log.trace(cmd)
def result = []
def value = (cmd.value ? "on" : "off")
def switchEvent = createEvent(name: "switch", value: value, descriptionText: "$device.displayName was turned $value")
result << switchEvent
if (cmd.value) {
result << createEvent(name: "level", value: cmd.value, unit: "%")
}
if (switchEvent.isStateChange) {
result << response(["delay 3000", zwave.meterV2.meterGet(scale: 2).format()])
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.meterv3.MeterReport cmd) {
log.trace(cmd)
if (cmd.meterType == 1) {
if (cmd.scale == 0) {
return createEvent(name: "energy", value: cmd.scaledMeterValue, unit: "kWh")
} else if (cmd.scale == 1) {
return createEvent(name: "energy", value: cmd.scaledMeterValue, unit: "kVAh")
} else if (cmd.scale == 2) {
return createEvent(name: "power", value: Math.round(cmd.scaledMeterValue), unit: "W")
} else {
return createEvent(name: "electric", value: cmd.scaledMeterValue, unit: ["pulses", "V", "A", "R/Z", ""][cmd.scale - 3])
}
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.trace(cmd)
log.debug "No handler for $cmd"
// Handles all Z-Wave commands we aren't interested in
createEvent(descriptionText: cmd.toString(), isStateChange: false)
}
def on() {
log.trace("on")
secureSequence([
zwave.basicV1.basicSet(value: 0xFF),
zwave.switchMultilevelV1.switchMultilevelGet()
])
}
def off() {
log.trace("off")
secureSequence([
zwave.basicV1.basicSet(value: 0x00),
zwave.switchMultilevelV1.switchMultilevelGet()
])
}
def poll() {
log.trace("poll")
secureSequence([
zwave.meterV2.meterGet(scale: 0),
zwave.meterV2.meterGet(scale: 2)
])
}
def refresh() {
log.trace("trace")
secureSequence([
zwave.meterV2.meterGet(scale: 0),
zwave.meterV2.meterGet(scale: 2)
])
}
def reset() {
log.trace("reset")
return secureSequence([
zwave.switchMultilevelV1.switchMultilevelGet(),
zwave.meterV2.meterReset(),
zwave.meterV2.meterGet(scale: 0),
zwave.meterV2.meterGet(scale: 2)
])
}
def setLevel(level) {
log.trace("setlevel")
if(level > 99) level = 99
secureSequence([
zwave.basicV1.basicSet(value: level),
zwave.switchMultilevelV1.switchMultilevelGet()
], 5000)
}
def configureAfterSecure() {
log.debug "configureAfterSecure()"
def cmds = secureSequence([
zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1, scaledConfigurationValue: param1.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, scaledConfigurationValue: param2.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: param3.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 2, scaledConfigurationValue: param4.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: param5.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 6, size: 2, scaledConfigurationValue: param6.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 7, size: 1, scaledConfigurationValue: param7.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 8, size: 2, scaledConfigurationValue: param8.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 9, size: 1, scaledConfigurationValue: param9.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 10, size: 2, scaledConfigurationValue: param10.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 11, size: 2, scaledConfigurationValue: param11.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 13, size: 1, scaledConfigurationValue: param13.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 15, size: 1, scaledConfigurationValue: param15.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 16, size: 2, scaledConfigurationValue: param16.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 19, size: 1, scaledConfigurationValue: param19.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 20, size: 1, scaledConfigurationValue: param20.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 21, size: 1, scaledConfigurationValue: param21.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 22, size: 1, scaledConfigurationValue: param22.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 23, size: 1, scaledConfigurationValue: param23.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 24, size: 1, scaledConfigurationValue: param24.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 25, size: 1, scaledConfigurationValue: param25.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 26, size: 1, scaledConfigurationValue: param26.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 27, size: 1, scaledConfigurationValue: param27.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 28, size: 1, scaledConfigurationValue: param28.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 29, size: 1, scaledConfigurationValue: param29.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 30, size: 1, scaledConfigurationValue: param30.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 32, size: 1, scaledConfigurationValue: param32.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 34, size: 1, scaledConfigurationValue: param34.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 35, size: 1, scaledConfigurationValue: param35.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 37, size: 1, scaledConfigurationValue: param37.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 39, size: 2, scaledConfigurationValue: param39.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: param40.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 41, size: 1, scaledConfigurationValue: param41.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 42, size: 1, scaledConfigurationValue: param42.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 43, size: 1, scaledConfigurationValue: param43.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 44, size: 2, scaledConfigurationValue: param44.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 45, size: 1, scaledConfigurationValue: param45.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 46, size: 1, scaledConfigurationValue: param46.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 47, size: 1, scaledConfigurationValue: param47.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 48, size: 1, scaledConfigurationValue: param48.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 49, size: 1, scaledConfigurationValue: param49.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 50, size: 1, scaledConfigurationValue: param50.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 52, size: 2, scaledConfigurationValue: param52.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 53, size: 2, scaledConfigurationValue: param53.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 54, size: 1, scaledConfigurationValue: param54.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 58, size: 1, scaledConfigurationValue: param58.toInteger()),
zwave.configurationV1.configurationSet(parameterNumber: 59, size: 2, scaledConfigurationValue: param59.toInteger())
])
// Register for Group 1
if(paramAssociationGroup1) {
cmds << secure(zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId]))
}
else {
cmds << secure(zwave.associationV2.associationRemove(groupingIdentifier:1, nodeId: [zwaveHubNodeId]))
}
// Register for Group 2
if(paramAssociationGroup2) {
cmds << secure(zwave.associationV2.associationSet(groupingIdentifier:2, nodeId: [zwaveHubNodeId]))
}
else {
cmds << secure(zwave.associationV2.associationRemove(groupingIdentifier:2, nodeId: [zwaveHubNodeId]))
}
// Register for Group 3
if(paramAssociationGroup3) {
cmds << secure(zwave.associationV2.associationSet(groupingIdentifier:3, nodeId: [zwaveHubNodeId]))
}
else {
cmds << secure(zwave.associationV2.associationRemove(groupingIdentifier:3, nodeId: [zwaveHubNodeId]))
}
// Register for Group 4
if(paramAssociationGroup4) {
cmds << secure(zwave.associationV2.associationSet(groupingIdentifier:4, nodeId: [zwaveHubNodeId]))
}
else {
cmds << secure(zwave.associationV2.associationRemove(groupingIdentifier:4, nodeId: [zwaveHubNodeId]))
}
// Register for Group 5
if(paramAssociationGroups5) {
cmds << secure(zwave.associationV2.associationSet(groupingIdentifier:5, nodeId: [zwaveHubNodeId]))
}
else {
cmds << secure(zwave.associationV2.associationRemove(groupingIdentifier:5, nodeId: [zwaveHubNodeId]))
}
cmds
}
def configure() {
// Wait until after the secure exchange for this
log.debug "configure()"
}
def updated() {
log.debug "updated()"
response(["delay 2000"] + configureAfterSecure() + refresh())
}
private secure(physicalgraph.zwave.Command cmd) {
log.trace(cmd)
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
private secureSequence(commands, delay=200) {
log.debug "$commands"
delayBetween(commands.collect{ secure(it) }, delay)
}

View File

@@ -21,7 +21,6 @@ metadata {
attribute "tamper", "enum", ["detected", "clear"]
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
fingerprint mfr:"010F", prod:"0C02", model:"1002"
}
simulator {
//battery

View File

@@ -682,7 +682,7 @@ def setHeatingSetpoint(degrees) {
def temperatureScale = getTemperatureScale()
def degreesInteger = degrees as Integer
sendEvent("name":"heatingSetpoint", "value":degreesInteger, "unit":temperatureScale)
sendEvent("name":"heatingSetpoint", "value":degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius*100) + "}"
@@ -691,7 +691,7 @@ def setHeatingSetpoint(degrees) {
def setCoolingSetpoint(degrees) {
def degreesInteger = degrees as Integer
sendEvent("name":"coolingSetpoint", "value":degreesInteger, "unit":temperatureScale)
sendEvent("name":"coolingSetpoint", "value":degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius*100) + "}"

View File

@@ -87,7 +87,7 @@ metadata {
def parse(String description) {
def resultMap = zigbee.getEvent(description)
if (resultMap) {
if (resultMap.name != "level" || resultMap.value != 0) { // Ignore level reports of 0 sent when bulb turns off
if ((resultMap.name == "level" && state.trigger == "setLevel") || resultMap.name != "level") { //doing this to account for weird level reporting bug with GE Link Bulbs
sendEvent(resultMap)
}
}
@@ -188,10 +188,12 @@ def updated() {
}
def on() {
state.trigger = "on/off"
zigbee.on()
}
def off() {
state.trigger = "on/off"
zigbee.off()
}
@@ -204,6 +206,7 @@ def refresh() {
}
def setLevel(value) {
state.trigger = "setLevel"
def cmd
def delayForRefresh = 500
if (dimRate && (state?.rate != null)) {

View File

@@ -0,0 +1,351 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* GE/Jasco ZigBee Dimmer
*
* Author: SmartThings
* Date: 2015-07-01
*/
metadata {
definition (name: "GE ZigBee Dimmer", namespace: "smartthings", author: "SmartThings") {
capability "Switch"
capability "Switch Level"
capability "Power Meter"
capability "Configuration"
capability "Refresh"
capability "Actuator"
capability "Sensor"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
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"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "level", label: 'Level ${currentValue}%'
}
valueTile("power", "device.power", decoration: "flat", width: 2, height: 2) {
state "power", label:'${currentValue} W'
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch", "level", "power","levelSliderControl","refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10
sendEvent(name: "power", value: powerValue)
/*
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
power level is an integer. The exact power level with correct units needs to be handled in the device type
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
*/
}
else {
sendEvent(name: finalResult.type, value: finalResult.value)
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
}
// Commands to device
def zigbeeCommand(cluster, attribute){
["st cmd 0x${device.deviceNetworkId} ${endpointId} ${cluster} ${attribute} {}"]
}
def off() {
zigbeeCommand("6", "0")
}
def on() {
zigbeeCommand("6", "1")
}
def setLevel(value) {
value = value as Integer
if (value == 0) {
off()
}
else {
sendEvent(name: "level", value: value)
setLevelWithRate(value, "0000") + ["delay 1000"] + on() //value is between 0 to 100; GE does NOT switch on if OFF
}
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0702 0x0400", "delay 500"
]
}
def configure() {
onOffConfig() + levelConfig() + powerConfig() + refresh()
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}
def parseDescriptionAsMap(description) {
if (description?.startsWith("read attr -")) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
}
else if (description?.startsWith("catchall: ")) {
def seg = (description - "catchall: ").split(" ")
def zigbeeMap = [:]
zigbeeMap += [raw: (description - "catchall: ")]
zigbeeMap += [profileId: seg[0]]
zigbeeMap += [clusterId: seg[1]]
zigbeeMap += [sourceEndpoint: seg[2]]
zigbeeMap += [destinationEndpoint: seg[3]]
zigbeeMap += [options: seg[4]]
zigbeeMap += [messageType: seg[5]]
zigbeeMap += [dni: seg[6]]
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
zigbeeMap += [manufacturerId: seg[9]]
zigbeeMap += [command: seg[10]]
zigbeeMap += [direction: seg[11]]
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
it.join('')
} : []]
zigbeeMap
}
}
def isKnownDescription(description) {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = parseDescriptionAsMap(description)
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
isDescriptionOnOff(descMap)
}
else if (descMap.cluster == "0008" || descMap.clusterId == "0008"){
isDescriptionLevel(descMap)
}
else if (descMap.cluster == "0702" || descMap.clusterId == "0702"){
isDescriptionPower(descMap)
}
else {
return "false"
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def isDescriptionOnOff(descMap) {
def switchValue = "undefined"
if (descMap.cluster == "0006") { //cluster info from read attr
value = descMap.value
if (value == "01"){
switchValue = "on"
}
else if (value == "00"){
switchValue = "off"
}
}
else if (descMap.clusterId == "0006") {
//cluster info from catch all
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
switchValue = "on"
}
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
switchValue = "off"
}
else if(descMap.command=="07"){
return [type: "update", value : "switch (0006) capability configured successfully"]
}
}
if (switchValue != "undefined"){
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
//@return - false or "success" or level [0-100]
def isDescriptionLevel(descMap) {
def dimmerValue = -1
if (descMap.cluster == "0008"){
//TODO: the message returned with catchall is command 0B with clusterId 0008. That is just a confirmation message
def value = convertHexToInt(descMap.value)
dimmerValue = Math.round(value * 100 / 255)
if(dimmerValue==0 && value > 0) {
dimmerValue = 1 //handling for non-zero hex value less than 3
}
}
else if(descMap.clusterId == "0008") {
if(descMap.command=="0B"){
return [type: "update", value : "level updated successfully"] //device updating the level change was successful. no value sent.
}
else if(descMap.command=="07"){
return [type: "update", value : "level (0008) capability configured successfully"]
}
}
if (dimmerValue != -1){
return [type: "level", value : dimmerValue]
}
else {
return "false"
}
}
def isDescriptionPower(descMap) {
def powerValue = "undefined"
if (descMap.cluster == "0702") {
if (descMap.attrId == "0400") {
powerValue = convertHexToInt(descMap.value)
}
}
else if (descMap.clusterId == "0702") {
if(descMap.command=="07"){
return [type: "update", value : "power (0702) capability configured successfully"]
}
}
if (powerValue != "undefined"){
return [type: "power", value : powerValue]
}
else {
return "false"
}
}
def onOffConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
//level config for devices with min reporting interval as 5 seconds and reporting interval if no activity as 1hour (3600s)
//min level change is 01
def levelConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 8 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 8 0 0x20 1 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
//min change in value is 05
def powerConfig() {
[
//Meter (Power) Reporting
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0702 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0702 0x0400 0x2A 1 600 {05}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
def setLevelWithRate(level, rate) {
if(rate == null){
rate = "0000"
}
level = convertToHexString(level * 255 / 100) //Converting the 0-100 range to 0-FF range in hex
["st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}"]
}
String convertToHexString(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}

View File

@@ -0,0 +1,284 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* GE/Jasco ZigBee Switch
*
* Author: SmartThings
* Date: 2015-07-01
*/
metadata {
// Automatically generated. Make future change here.
definition (name: "GE ZigBee Switch", namespace: "smartthings", author: "SmartThings") {
capability "Switch"
capability "Power Meter"
capability "Configuration"
capability "Refresh"
capability "Actuator"
capability "Sensor"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
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"
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
valueTile("power", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "power", label:'${currentValue} Watts'
}
main "switch"
details(["switch", "power", "refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = isKnownDescription(description)
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10
sendEvent(name: "power", value: powerValue)
/*
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
power level is an integer. The exact power level with correct units needs to be handled in the device type
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
*/
}
else {
sendEvent(name: finalResult.type, value: finalResult.value)
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
}
// Commands to device
def zigbeeCommand(cluster, attribute){
"st cmd 0x${device.deviceNetworkId} ${endpointId} ${cluster} ${attribute} {}"
}
def off() {
zigbeeCommand("6", "0")
}
def on() {
zigbeeCommand("6", "1")
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0702 0x0400", "delay 500"
]
}
def configure() {
onOffConfig() + powerConfig() + refresh()
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}
def parseDescriptionAsMap(description) {
if (description?.startsWith("read attr -")) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
}
else if (description?.startsWith("catchall: ")) {
def seg = (description - "catchall: ").split(" ")
def zigbeeMap = [:]
zigbeeMap += [raw: (description - "catchall: ")]
zigbeeMap += [profileId: seg[0]]
zigbeeMap += [clusterId: seg[1]]
zigbeeMap += [sourceEndpoint: seg[2]]
zigbeeMap += [destinationEndpoint: seg[3]]
zigbeeMap += [options: seg[4]]
zigbeeMap += [messageType: seg[5]]
zigbeeMap += [dni: seg[6]]
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
zigbeeMap += [manufacturerId: seg[9]]
zigbeeMap += [command: seg[10]]
zigbeeMap += [direction: seg[11]]
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
it.join('')
} : []]
zigbeeMap
}
}
def isKnownDescription(description) {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = parseDescriptionAsMap(description)
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
isDescriptionOnOff(descMap)
}
else if (descMap.cluster == "0702" || descMap.clusterId == "0702"){
isDescriptionPower(descMap)
}
else {
return "false"
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def isDescriptionOnOff(descMap) {
def switchValue = "undefined"
if (descMap.cluster == "0006") { //cluster info from read attr
value = descMap.value
if (value == "01"){
switchValue = "on"
}
else if (value == "00"){
switchValue = "off"
}
}
else if (descMap.clusterId == "0006") {
//cluster info from catch all
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
switchValue = "on"
}
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
switchValue = "off"
}
else if(descMap.command=="07"){
return [type: "update", value : "switch (0006) capability configured successfully"]
}
}
if (switchValue != "undefined"){
return [type: "switch", value : switchValue]
}
else {
return "false"
}
}
def isDescriptionPower(descMap) {
def powerValue = "undefined"
if (descMap.cluster == "0702") {
if (descMap.attrId == "0400") {
powerValue = convertHexToInt(descMap.value)
}
}
else if (descMap.clusterId == "0702") {
if(descMap.command=="07"){
return [type: "update", value : "power (0702) capability configured successfully"]
}
}
if (powerValue != "undefined"){
return [type: "power", value : powerValue]
}
else {
return "false"
}
}
def onOffConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
//min change in value is 05
def powerConfig() {
[
//Meter (Power) Reporting
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0702 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0702 0x0400 0x2A 1 600 {05}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
String convertToHexString(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}

View File

@@ -16,7 +16,6 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
command "setAdjustedColor"
command "reset"
@@ -44,7 +43,7 @@ metadata {
}
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single"
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
}
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -52,14 +51,10 @@ metadata {
}
main(["rich-control"])
details(["rich-control", "reset", "refresh"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
}
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -80,78 +75,118 @@ def parse(description) {
// handle commands
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) {
log.trace parent.setLevel(this, percent)
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
}
}
void setSaturation(percent) {
log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) {
log.trace parent.setSaturation(this, percent)
parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
}
}
void setHue(percent) {
log.debug "Executing 'setHue'"
if (verifyPercent(percent)) {
log.trace parent.setHue(this, percent)
parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
}
}
void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = []
def validValues = [:]
if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue
}
if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation
}
if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex
} else {
log.warn "$value.hex is not a valid color"
}
}
if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level
}
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off"
} else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on"
}
if (!validValues.isEmpty()) {
log.trace parent.setColor(this, validValues)
if (!events.isEmpty()) {
parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
}
}
void reset() {
log.debug "Executing 'reset'"
def value = [hue:20, saturation:2]
setAdjustedColor(value)
def value = [level:100, saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
}
void setAdjustedColor(value) {
if (value) {
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
setColor(adjusted)
} else {
log.warn "Invalid color input $value"
log.warn "Invalid color input"
}
}
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else {
log.warn "Invalid color temperature"
}
}
@@ -160,6 +195,22 @@ void refresh() {
parent.manualRefresh()
}
def adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}
def verifyPercent(percent) {
if (percent == null)
@@ -171,4 +222,3 @@ def verifyPercent(percent) {
return false
}
}

View File

@@ -7,15 +7,8 @@
metadata {
// Automatically generated. Make future change here.
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
capability "Health Check"
attribute "serialNumber", "string"
attribute "networkAddress", "string"
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
// Possible values "Online" or "Offline"
attribute "status", "string"
// Id is the number on the back of the hub, Hue uses last six digits of Mac address
// This is also used in the Hue application as ID
attribute "idNumber", "string"
}
simulator {
@@ -24,30 +17,25 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"rich-control"){
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
attributeState "Offline", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#ffffff"
attributeState "Online", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#79b821"
tileAttribute ("", key: "PRIMARY_CONTROL") {
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
}
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
attributeState "default", label:'SN: ${currentValue}'
}
valueTile("doNotRemove", "v", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
state "default", label:'If removed, Hue lights will not work properly'
}
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
state "default", label:'SN: ${currentValue}'
}
valueTile("idNumber", "device.idNumber", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
state "default", label:'ID: ${currentValue}'
}
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
state "default", label:'IP: ${currentValue}'
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 4, inactiveLabel: false) {
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
}
main (["rich-control"])
details(["rich-control", "doNotRemove", "idNumber", "networkAddress"])
details(["rich-control", "networkAddress"])
}
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
}
// parse events into attributes
def parse(description) {
log.debug "Parsing '${description}'"
@@ -68,7 +56,7 @@ def parse(description) {
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
results << createEvent(name: "${map.name}", value: "${map.value}")
} else {
log.trace "Parsing description"
log.trace "Parsing description"
def msg = parseLanMessage(description)
if (msg.body) {
def contentType = msg.headers["Content-Type"]
@@ -76,14 +64,18 @@ def parse(description) {
def bulbs = new groovy.json.JsonSlurper().parseText(msg.body)
if (bulbs.state) {
log.info "Bridge response: $msg.body"
} else {
// Sending Bulbs List to parent"
if (parent.state.inBulbDiscovery)
log.info parent.bulbListHandler(device.hub.id, msg.body)
}
} else if (contentType?.contains("xml")) {
}
else if (contentType?.contains("xml")) {
log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body)
parent.hubVerification(device.hub.id, msg.body)
}
}
}
}
results
}

View File

@@ -17,7 +17,6 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
command "setAdjustedColor"
command "reset"
@@ -44,16 +43,16 @@ metadata {
}
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: 'WHITES'
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single"
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
}
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -65,10 +64,6 @@ metadata {
}
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -89,92 +84,141 @@ def parse(description) {
// handle commands
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) {
log.trace parent.setLevel(this, percent)
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
}
}
void setSaturation(percent) {
log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) {
log.trace parent.setSaturation(this, percent)
parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
}
}
void setHue(percent) {
log.debug "Executing 'setHue'"
if (verifyPercent(percent)) {
log.trace parent.setHue(this, percent)
parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
}
}
void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = []
def validValues = [:]
if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue
}
if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation
}
if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex
} else {
log.warn "$value.hex is not a valid color"
}
}
if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level
}
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off"
} else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on"
}
if (!validValues.isEmpty()) {
log.trace parent.setColor(this, validValues)
if (!events.isEmpty()) {
parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
}
}
void reset() {
log.debug "Executing 'reset'"
setColorTemperature(4000)
def value = [level:100, saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
}
void setAdjustedColor(value) {
if (value) {
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
setColor(adjusted)
} else {
log.warn "Invalid color input $value"
log.warn "Invalid color input"
}
}
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
log.trace parent.setColorTemperature(this, value)
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else {
log.warn "Invalid color temperature $value"
log.warn "Invalid color temperature"
}
}
void refresh() {
log.debug "Executing 'refresh'"
parent?.manualRefresh()
parent.manualRefresh()
}
def adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}
def verifyPercent(percent) {
@@ -187,4 +231,3 @@ def verifyPercent(percent) {
return false
}
}

View File

@@ -14,7 +14,6 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
command "refresh"
}
@@ -49,10 +48,6 @@ metadata {
}
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -73,16 +68,20 @@ def parse(description) {
// handle commands
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (percent != null && percent >= 0 && percent <= 100) {
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
sendEvent(name: "switch", value: "on")
} else {
log.warn "$percent is not 0-100"
}
@@ -92,4 +91,3 @@ void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}

View File

@@ -1,109 +0,0 @@
/**
* Hue White Ambiance Bulb
*
* Philips Hue Type "Color Temperature Light"
*
* Author: SmartThings
*/
// for the UI
metadata {
// Automatically generated. Make future change here.
definition (name: "Hue White Ambiance Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Color Temperature"
capability "Switch"
capability "Refresh"
capability "Health Check"
command "refresh"
}
simulator {
// TODO: define status and reply messages here
}
tiles (scale: 2){
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2200..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: 'WHITES'
}
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["rich-control"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
void installed() {
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
def results = []
def map = description
if (description instanceof String) {
log.debug "Hue Ambience Bulb stringToMap - ${map}"
map = stringToMap(description)
}
if (map?.name && map?.value) {
results << createEvent(name: "${map?.name}", value: "${map?.value}")
}
results
}
// handle commands
void on() {
log.trace parent.on(this)
}
void off() {
log.trace parent.off(this)
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (percent != null && percent >= 0 && percent <= 100) {
log.trace parent.setLevel(this, percent)
} else {
log.warn "$percent is not 0-100"
}
}
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
log.trace parent.setColorTemperature(this, value)
} else {
log.warn "Invalid color temperature"
}
}
void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}

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

@@ -13,7 +13,6 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -144,14 +143,51 @@ private Map parseReportAttributeMessage(String description) {
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
Map resultMap = [:]
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0030': // Closed/No Motion/Dry
log.debug 'no motion'
resultMap.name = 'motion'
resultMap.value = 'inactive'
break
resultMap.name = 'motion'
resultMap.value = zs.isAlarm2Set() ? 'active' : 'inactive'
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
case '0x0032': // Open/Motion/Wet
log.debug 'motion'
resultMap.name = 'motion'
resultMap.value = 'active'
break
return resultMap
case '0x0032': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap.name = 'motion'
resultMap.value = 'active'
break
case '0x0033': // Battery Alarm
break
case '0x0034': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap.name = 'motion'
resultMap.value = 'inactive'
break
case '0x0035': // Restore Report
break
case '0x0036': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap.name = 'motion'
resultMap.value = 'active'
break
case '0x0038': // Test Mode
break
}
return resultMap
}
def refresh()

View File

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

View File

@@ -1,41 +0,0 @@
# Nyce Door/Window Sensor (Open/Close Sensor)
Cloud Execution
Works with:
* [NYCE Door/Window Sensor NCZ-3011](https://support.smartthings.com/hc/en-us/articles/204576764-NYCE-Door-Window-Sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
* [Troubleshooting](#troubleshooting)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Contact Sensor** - can detect contact (with possible values - open/closed)
* **Battery** - defines device uses a battery
* **Refresh** - _refresh()_ command for status updates
* **Health Check** - indicates ability to get device health notifications
## Device Health
Nyce Door/Window sensor with reporting interval of 5 min.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* __12min__ checkInterval
## Battery Specification
One 3V CR2032 battery required.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
* [Nyce Door/Window Sensor](https://support.smartthings.com/hc/en-us/articles/204576764-NYCE-Door-Window-Sensor)

View File

@@ -13,32 +13,28 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
capability "Battery"
capability "Battery"
capability "Configuration"
capability "Contact Sensor"
capability "Contact Sensor"
capability "Refresh"
capability "Health Check"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3010", deviceJoinName: "NYCE Door Hinge Sensor"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3010", deviceJoinName: "NYCE Door Hinge Sensor"
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
}
simulator {
}
tiles {
standardTile("contact", "device.contact", width: 2, height: 2) {
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
@@ -223,33 +219,40 @@ private Map parseReportAttributeMessage(String description) {
}
private List parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
log.debug "parseIasMessage: $description"
List parsedMsg = description.split(" ")
String msgCode = parsedMsg[2]
List resultListMap = []
Map resultMap_battery = [:]
Map resultMap_battery_state = [:]
Map resultMap_sensor = [:]
resultMap_sensor.name = "contact"
resultMap_sensor.value = zs.isAlarm1Set() ? "open" : "closed"
// Relevant bit field definitions from ZigBee spec
def BATTERY_BIT = ( 1 << 3 )
def TROUBLE_BIT = ( 1 << 6 )
def SENSOR_BIT = ( 1 << 0 ) // it's ALARM1 bit from the ZCL spec
// Convert hex string to integer
def zoneStatus = Integer.parseInt(msgCode[-4..-1],16)
log.debug "parseIasMessage: zoneStatus: ${zoneStatus}"
// Check each relevant bit, create map for it, and add to list
log.debug "parseIasMessage: Battery Status ${zs.battery}"
log.debug "parseIasMessage: Trouble Status ${zs.trouble}"
log.debug "parseIasMessage: Sensor Status ${zs.alarm1}"
log.debug "parseIasMessage: Battery Status ${zoneStatus & BATTERY_BIT}"
log.debug "parseIasMessage: Trouble Status ${zoneStatus & TROUBLE_BIT}"
log.debug "parseIasMessage: Sensor Status ${zoneStatus & SENSOR_BIT}"
/* Comment out this path to check the battery state to avoid overwriting the
battery value (Change log #2), but keep these conditions for later use
resultMap_battery_state.name = "battery_state"
if (zs.isTroubleSet()) {
if (zoneStatus & TROUBLE_BIT) {
resultMap_battery_state.value = "failed"
resultMap_battery.name = "battery"
resultMap_battery.value = 0
}
else {
if (zs.isBatterySet()) {
if (zoneStatus & BATTERY_BIT) {
resultMap_battery_state.value = "low"
// to generate low battery notification by the platform
@@ -267,6 +270,9 @@ private List parseIasMessage(String description) {
}
*/
resultMap_sensor.name = "contact"
resultMap_sensor.value = (zoneStatus & SENSOR_BIT) ? "open" : "closed"
resultListMap << resultMap_battery_state
resultListMap << resultMap_battery
resultListMap << resultMap_sensor
@@ -274,28 +280,23 @@ private List parseIasMessage(String description) {
return resultListMap
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
def enrollCmds = [
def configCmds = [
//battery reporting and heartbeat
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500",
// Writes CIE attribute on end device to direct reports to the hub's EUID
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
log.debug "configure: Write IAS CIE"
// battery minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
return enrollCmds + zigbee.batteryConfig(30, 300) + refresh() // send refresh cmds as part of config
return configCmds
}
def enrollResponse() {
@@ -340,8 +341,7 @@ Integer convertHexToInt(hex) {
def refresh() {
log.debug "Refreshing Battery"
def refreshCmds = [
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 1 0x20", "delay 200"
]
return refreshCmds + enrollResponse()
}

View File

@@ -0,0 +1,384 @@
/*
Osram Lightify Gardenspot Mini RGB
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
that issue by using state variables
*/
metadata {
definition (name: "OSRAM LIGHTIFY Gardenspot mini RGB", namespace: "smartthings", author: "SmartThings") {
capability "Color Temperature"
capability "Actuator"
capability "Switch"
capability "Switch Level"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Color Control"
attribute "colorName", "string"
command "setAdjustedColor"
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"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
state "color", action:"setAdjustedColor"
}
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat") {
state "colorName", label: '${currentValue}'
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
main(["switch"])
details(["switch", "refresh", "colorName", "levelSliderControl", "level", "rgbSelector"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
//log.info "description is $description"
if (description?.startsWith("catchall:")) {
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
{
def result = createEvent(name: "switch", value: "on")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
{
if(!(description?.startsWith("catchall: 0104 0300"))){
def result = createEvent(name: "switch", value: "off")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
}
else if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.trace "descMap : $descMap"
if (descMap.cluster == "0300") {
if(descMap.attrId == "0000"){ //Hue Attribute
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
log.debug "Hue value returned is $hueValue"
sendEvent(name: "hue", value: hueValue, displayed:false)
}
else if(descMap.attrId == "0001"){ //Saturation Attribute
def saturationValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
log.debug "Saturation from refresh is $saturationValue"
sendEvent(name: "saturation", value: saturationValue, displayed:false)
}
}
else if(descMap.cluster == "0008"){
def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
log.debug "dimmer value is $dimmerValue"
sendEvent(name: "level", value: dimmerValue)
}
}
else {
def name = description?.startsWith("on/off: ") ? "switch" : null
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
def result = createEvent(name: name, value: value)
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
def on() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
setLevel(state?.levelValue)
}
def zigbeeOff() {
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
def off() {
log.debug "off()"
sendEvent(name: "switch", value: "off")
zigbeeOff()
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1"
]
}
def configure() {
state.levelValue = 100
log.debug "Configuring Reporting and Bindings."
def configCmds = [
//Switch Reporting
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1000",
//Level Control Reporting
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def poll(){
log.debug "Poll is calling refresh"
refresh()
}
def zigbeeSetLevel(level) {
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
}
def setLevel(value) {
state.levelValue = (value==null) ? 100 : value
log.trace "setLevel($value)"
def cmds = []
if (value == 0) {
sendEvent(name: "switch", value: "off")
cmds << zigbeeOff()
}
else if (device.latestValue("switch") == "off") {
sendEvent(name: "switch", value: "on")
}
sendEvent(name: "level", value: state.levelValue)
def level = hex(state.levelValue * 255 / 100)
cmds << zigbeeSetLevel(level)
//log.debug cmds
cmds
}
//input Hue Integer values; returns color name for saturation 100%
private getColorName(hueValue){
if(hueValue>360 || hueValue<0)
return
hueValue = Math.round(hueValue / 100 * 360)
log.debug "hue value is $hueValue"
def colorName = "Color Mode"
if(hueValue>=0 && hueValue <= 4){
colorName = "Red"
}
else if (hueValue>=5 && hueValue <=21 ){
colorName = "Brick Red"
}
else if (hueValue>=22 && hueValue <=30 ){
colorName = "Safety Orange"
}
else if (hueValue>=31 && hueValue <=40 ){
colorName = "Dark Orange"
}
else if (hueValue>=41 && hueValue <=49 ){
colorName = "Amber"
}
else if (hueValue>=50 && hueValue <=56 ){
colorName = "Gold"
}
else if (hueValue>=57 && hueValue <=65 ){
colorName = "Yellow"
}
else if (hueValue>=66 && hueValue <=83 ){
colorName = "Electric Lime"
}
else if (hueValue>=84 && hueValue <=93 ){
colorName = "Lawn Green"
}
else if (hueValue>=94 && hueValue <=112 ){
colorName = "Bright Green"
}
else if (hueValue>=113 && hueValue <=135 ){
colorName = "Lime"
}
else if (hueValue>=136 && hueValue <=166 ){
colorName = "Spring Green"
}
else if (hueValue>=167 && hueValue <=171 ){
colorName = "Turquoise"
}
else if (hueValue>=172 && hueValue <=187 ){
colorName = "Aqua"
}
else if (hueValue>=188 && hueValue <=203 ){
colorName = "Sky Blue"
}
else if (hueValue>=204 && hueValue <=217 ){
colorName = "Dodger Blue"
}
else if (hueValue>=218 && hueValue <=223 ){
colorName = "Navy Blue"
}
else if (hueValue>=224 && hueValue <=251 ){
colorName = "Blue"
}
else if (hueValue>=252 && hueValue <=256 ){
colorName = "Han Purple"
}
else if (hueValue>=257 && hueValue <=274 ){
colorName = "Electric Indigo"
}
else if (hueValue>=275 && hueValue <=289 ){
colorName = "Electric Purple"
}
else if (hueValue>=290 && hueValue <=300 ){
colorName = "Orchid Purple"
}
else if (hueValue>=301 && hueValue <=315 ){
colorName = "Magenta"
}
else if (hueValue>=316 && hueValue <=326 ){
colorName = "Hot Pink"
}
else if (hueValue>=327 && hueValue <=335 ){
colorName = "Deep Pink"
}
else if (hueValue>=336 && hueValue <=339 ){
colorName = "Raspberry"
}
else if (hueValue>=340 && hueValue <=352 ){
colorName = "Crimson"
}
else if (hueValue>=353 && hueValue <=360 ){
colorName = "Red"
}
colorName
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private evenHex(value){
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() % 2 != 0) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}
def setAdjustedColor(value) {
log.debug "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.level = null // needed because color picker always sends 100
setColor(adjusted)
}
def setColor(value){
log.trace "setColor($value)"
def max = 0xfe
if (value.hex) { sendEvent(name: "color", value: value.hex, displayed:false)}
def colorName = getColorName(value.hue)
sendEvent(name: "colorName", value: colorName)
log.debug "color name is : $colorName"
sendEvent(name: "hue", value: value.hue, displayed:false)
sendEvent(name: "saturation", value: value.saturation, displayed:false)
def scaledHueValue = evenHex(Math.round(value.hue * max / 100.0))
def scaledSatValue = evenHex(Math.round(value.saturation * max / 100.0))
def cmd = []
if (value.switch != "off" && device.latestValue("switch") == "off") {
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
cmd << "delay 150"
}
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${scaledHueValue} 00 0000}"
cmd << "delay 150"
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${scaledSatValue} 0000}"
if (value.level) {
state.levelValue = value.level
sendEvent(name: "level", value: value.level)
def level = hex(value.level * 255 / 100)
cmd << zigbeeSetLevel(level)
}
if (value.switch == "off") {
cmd << "delay 150"
cmd << off()
}
cmd
}

View File

@@ -0,0 +1,459 @@
/*
Osram Flex RGBW Light Strip
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
that issue by using state variables
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
capability "Color Temperature"
capability "Actuator"
capability "Switch"
capability "Switch Level"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Color Control"
attribute "colorName", "string"
command "setAdjustedColor"
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
state "colorTemperature", label: '${currentValue} K'
}
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat") {
state "colorName", label: '${currentValue}'
}
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
state "color", action:"setAdjustedColor"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
main(["switch"])
details(["switch", "refresh", "colorName", "levelSliderControl", "level", "colorTempSliderControl", "colorTemp", "rgbSelector"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
//log.info "description is $description"
if (description?.startsWith("catchall:")) {
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
{
def result = createEvent(name: "switch", value: "on")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
{
if(!(description?.startsWith("catchall: 0104 0300"))){
def result = createEvent(name: "switch", value: "off")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
}
else if (description?.startsWith("read attr -")) { //for values returned after hitting refresh
def descMap = parseDescriptionAsMap(description)
log.trace "descMap : $descMap"
if (descMap.cluster == "0300") {
if(descMap.attrId == "0007"){
log.debug "in read attr"
log.debug descMap.value
def tempInMired = convertHexToInt(descMap.value)
def tempInKelvin = Math.round(1000000/tempInMired)
log.trace "temp in kelvin: $tempInKelvin"
sendEvent(name: "colorTemperature", value: tempInKelvin, displayed:false)
}
else if(descMap.attrId == "0008"){ //Color mode attribute
if(descMap.value == "00"){
state.colorType = "rgb"
}else if(descMap.value == "02"){
state.colorType = "white"
}
}
else if(descMap.attrId == "0000"){ //Hue Attribute
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
log.debug "Hue value returned is $hueValue"
sendEvent(name: "hue", value: hueValue, displayed:false)
}
else if(descMap.attrId == "0001"){ //Saturation Attribute
def saturationValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
log.debug "Saturation from refresh is $saturationValue"
sendEvent(name: "saturation", value: saturationValue, displayed:false)
}
}
else if(descMap.cluster == "0008"){
def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
log.debug "dimmer value is $dimmerValue"
sendEvent(name: "level", value: dimmerValue)
}
}
else {
def name = description?.startsWith("on/off: ") ? "switch" : null
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
def result = createEvent(name: name, value: value)
log.debug "description is $description"
return result
}
}
def on() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
setLevel(state?.levelValue)
}
def zigbeeOff() {
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
def off() {
log.debug "off()"
sendEvent(name: "switch", value: "off")
zigbeeOff()
}
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 7", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 8"
]
}
def configure() {
state.levelValue = 100
state.colorType = "white"
log.debug "Configuring Reporting and Bindings."
def configCmds = [
//Switch Reporting
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1000",
//Level Control Reporting
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
}
def setColorTemperature(value) {
state?.colorType = "white"
if(value<101){
value = (value*38) + 2700 //Calculation of mapping 0-100 to 2700-6500
}
def tempInMired = Math.round(1000000/value)
def finalHex = swapEndianHex(hex(tempInMired, 4))
def genericName = getGenericName(value)
log.debug "generic name is : $genericName"
def cmds = []
sendEvent(name: "colorTemperature", value: value, displayed:false)
sendEvent(name: "colorName", value: genericName)
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0300 0x0a {${finalHex} 2000}"
cmds
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def poll(){
log.debug "Poll is calling refresh"
refresh()
}
def zigbeeSetLevel(level) {
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
}
def setLevel(value) {
state.levelValue = (value==null) ? 100 : value
log.trace "setLevel($value)"
def cmds = []
if (value == 0) {
sendEvent(name: "switch", value: "off")
cmds << zigbeeOff()
}
else if (device.latestValue("switch") == "off") {
sendEvent(name: "switch", value: "on")
}
sendEvent(name: "level", value: state.levelValue)
def level = hex(state.levelValue * 255 / 100)
cmds << zigbeeSetLevel(level)
//log.debug cmds
cmds
}
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
private getGenericName(value){
def genericName = "White"
if(state?.colorType == "rgb"){
genericName = "Color Mode"
}
else{
if(value < 3300){
genericName = "Soft White"
} else if(value < 4150){
genericName = "Moonlight"
} else if(value < 5000){
genericName = "Cool White"
} else if(value <= 6500){
genericName = "Daylight"
}
}
genericName
}
//input Hue Integer values; returns color name for saturation 100%
private getColorName(hueValue){
if(hueValue>360 || hueValue<0)
return
hueValue = Math.round(hueValue / 100 * 360)
log.debug "hue value is $hueValue"
def colorName = "Color Mode"
if(hueValue>=0 && hueValue <= 4){
colorName = "Red"
}
else if (hueValue>=5 && hueValue <=21 ){
colorName = "Brick Red"
}
else if (hueValue>=22 && hueValue <=30 ){
colorName = "Safety Orange"
}
else if (hueValue>=31 && hueValue <=40 ){
colorName = "Dark Orange"
}
else if (hueValue>=41 && hueValue <=49 ){
colorName = "Amber"
}
else if (hueValue>=50 && hueValue <=56 ){
colorName = "Gold"
}
else if (hueValue>=57 && hueValue <=65 ){
colorName = "Yellow"
}
else if (hueValue>=66 && hueValue <=83 ){
colorName = "Electric Lime"
}
else if (hueValue>=84 && hueValue <=93 ){
colorName = "Lawn Green"
}
else if (hueValue>=94 && hueValue <=112 ){
colorName = "Bright Green"
}
else if (hueValue>=113 && hueValue <=135 ){
colorName = "Lime"
}
else if (hueValue>=136 && hueValue <=166 ){
colorName = "Spring Green"
}
else if (hueValue>=167 && hueValue <=171 ){
colorName = "Turquoise"
}
else if (hueValue>=172 && hueValue <=187 ){
colorName = "Aqua"
}
else if (hueValue>=188 && hueValue <=203 ){
colorName = "Sky Blue"
}
else if (hueValue>=204 && hueValue <=217 ){
colorName = "Dodger Blue"
}
else if (hueValue>=218 && hueValue <=223 ){
colorName = "Navy Blue"
}
else if (hueValue>=224 && hueValue <=251 ){
colorName = "Blue"
}
else if (hueValue>=252 && hueValue <=256 ){
colorName = "Han Purple"
}
else if (hueValue>=257 && hueValue <=274 ){
colorName = "Electric Indigo"
}
else if (hueValue>=275 && hueValue <=289 ){
colorName = "Electric Purple"
}
else if (hueValue>=290 && hueValue <=300 ){
colorName = "Orchid Purple"
}
else if (hueValue>=301 && hueValue <=315 ){
colorName = "Magenta"
}
else if (hueValue>=316 && hueValue <=326 ){
colorName = "Hot Pink"
}
else if (hueValue>=327 && hueValue <=335 ){
colorName = "Deep Pink"
}
else if (hueValue>=336 && hueValue <=339 ){
colorName = "Raspberry"
}
else if (hueValue>=340 && hueValue <=352 ){
colorName = "Crimson"
}
else if (hueValue>=353 && hueValue <=360 ){
colorName = "Red"
}
colorName
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private evenHex(value){
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() % 2 != 0) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}
def setAdjustedColor(value) {
log.debug "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.level = null // needed because color picker always sends 100
setColor(adjusted)
}
def setColor(value){
state?.colorType = "rgb"
log.trace "setColor($value)"
def max = 0xfe
if (value.hex) { sendEvent(name: "color", value: value.hex, displayed:false)}
def colorName = getColorName(value.hue)
log.debug "color name is : $colorName"
sendEvent(name: "colorName", value: colorName)
sendEvent(name: "colorTemperature", value: "--", displayed:false)
sendEvent(name: "hue", value: value.hue, displayed:false)
sendEvent(name: "saturation", value: value.saturation, displayed:false)
def scaledHueValue = evenHex(Math.round(value.hue * max / 100.0))
def scaledSatValue = evenHex(Math.round(value.saturation * max / 100.0))
def cmd = []
if (value.switch != "off" && device.latestValue("switch") == "off") {
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
cmd << "delay 150"
}
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${scaledHueValue} 00 0000}"
cmd << "delay 150"
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${scaledSatValue} 0000}"
if (value.level) {
state.levelValue = value.level
sendEvent(name: "level", value: value.level)
def level = hex(value.level * 255 / 100)
cmd << zigbeeSetLevel(level)
}
if (value.switch == "off") {
cmd << "delay 150"
cmd << off()
}
cmd
}

View File

@@ -0,0 +1,261 @@
/*
Osram Tunable White 60 A19 bulb
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
that issue by using state variables
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "OSRAM LIGHTIFY LED Tunable White 60W", namespace: "smartthings", author: "SmartThings") {
capability "Color Temperature"
capability "Actuator"
capability "Switch"
capability "Switch Level"
capability "Configuration"
capability "Refresh"
capability "Sensor"
attribute "colorName", "string"
// indicates that device keeps track of heartbeat (in state.heartbeat)
attribute "heartbeat", "string"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
state "colorTemperature", label: '${currentValue} K'
}
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat") {
state "colorName", label: '${currentValue}'
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
main(["switch"])
details(["switch", "refresh", "colorName", "levelSliderControl", "level", "colorTempSliderControl", "colorTemp"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
//log.trace description
// save heartbeat (i.e. last time we got a message from device)
state.heartbeat = Calendar.getInstance().getTimeInMillis()
if (description?.startsWith("catchall:")) {
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
{
def result = createEvent(name: "switch", value: "on")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
{
def result = createEvent(name: "switch", value: "off")
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
else if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.trace "descMap : $descMap"
if (descMap.cluster == "0300") {
log.debug descMap.value
def tempInMired = convertHexToInt(descMap.value)
def tempInKelvin = Math.round(1000000/tempInMired)
log.trace "temp in kelvin: $tempInKelvin"
sendEvent(name: "colorTemperature", value: tempInKelvin, displayed:false)
}
else if(descMap.cluster == "0008"){
def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
log.debug "dimmer value is $dimmerValue"
sendEvent(name: "level", value: dimmerValue)
}
}
else {
def name = description?.startsWith("on/off: ") ? "switch" : null
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
def result = createEvent(name: name, value: value)
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
def on() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
setLevel(state?.levelValue)
}
def off() {
log.debug "off()"
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
def refresh() {
sendEvent(name: "heartbeat", value: "alive", displayed:false)
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 7"
]
}
def configure() {
state.levelValue = 100
log.debug "Configuring Reporting and Bindings."
def configCmds = [
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
]
return onOffConfig() + levelConfig() + configCmds + refresh() // send refresh cmds as part of config
}
def onOffConfig() {
[
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 6 0 0x10 0 300 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
//level config for devices with min reporting interval as 5 seconds and reporting interval if no activity as 1hour (3600s)
//min level change is 01
def levelConfig() {
[
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 8 0 0x20 5 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
def setColorTemperature(value) {
if(value<101){
value = (value*38) + 2700 //Calculation of mapping 0-100 to 2700-6500
}
def tempInMired = Math.round(1000000/value)
def finalHex = swapEndianHex(hex(tempInMired, 4))
def genericName = getGenericName(value)
log.debug "generic name is : $genericName"
def cmds = []
sendEvent(name: "colorTemperature", value: value, displayed:false)
sendEvent(name: "colorName", value: genericName)
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0300 0x0a {${finalHex} 2000}"
cmds
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def setLevel(value) {
state.levelValue = (value==null) ? 100 : value
log.trace "setLevel($value)"
def cmds = []
if (value == 0) {
sendEvent(name: "switch", value: "off")
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
else if (device.latestValue("switch") == "off") {
sendEvent(name: "switch", value: "on")
}
sendEvent(name: "level", value: state.levelValue)
def level = hex(state.levelValue * 254 / 100)
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
//log.debug cmds
cmds
}
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
private getGenericName(value){
def genericName = "White"
if(value < 3300){
genericName = "Soft White"
} else if(value < 4150){
genericName = "Moonlight"
} else if(value < 5000){
genericName = "Cool White"
} else if(value <= 6500){
genericName = "Daylight"
}
genericName
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
byte tmp;
tmp = array[1];
array[1] = array[0];
array[0] = tmp;
return array
}

View File

@@ -69,17 +69,15 @@ metadata {
def parse(String description) {
log.debug "description is $description"
def event = [:]
def finalResult = isKnownDescription(description)
if (finalResult) {
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
event = null
}
else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10
event = createEvent(name: "power", value: powerValue)
sendEvent(name: "power", value: powerValue)
/*
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
@@ -89,14 +87,13 @@ def parse(String description) {
*/
}
else {
event = createEvent(name: finalResult.type, value: finalResult.value)
sendEvent(name: finalResult.type, value: finalResult.value)
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug parseDescriptionAsMap(description)
}
return event
}
// Commands to device
@@ -136,7 +133,7 @@ def refresh() {
}
def configure() {
refresh() + onOffConfig() + levelConfig() + powerConfig()
onOffConfig() + levelConfig() + powerConfig() + refresh()
}
@@ -212,16 +209,13 @@ def isKnownDescription(description) {
else if (descMap.cluster == "0B04" || descMap.clusterId == "0B04"){
isDescriptionPower(descMap)
}
else {
return [:]
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return [:]
return "false"
}
}
@@ -255,7 +249,7 @@ def isDescriptionOnOff(descMap) {
return [type: "switch", value : switchValue]
}
else {
return [:]
return "false"
}
}
@@ -282,9 +276,10 @@ def isDescriptionLevel(descMap) {
if (dimmerValue != -1){
return [type: "level", value : dimmerValue]
}
else {
return [:]
return "false"
}
}
@@ -306,7 +301,7 @@ def isDescriptionPower(descMap) {
return [type: "power", value : powerValue]
}
else {
return [:]
return "false"
}
}

View File

@@ -47,21 +47,9 @@ def parse(String description) {
// Commands to device
def on() {
[
'zcl on-off on',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
'zcl on-off on'
}
def off() {
[
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
'zcl on-off off'
}

View File

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

View File

@@ -1,39 +0,0 @@
# SmartPower Outlet
Local Execution on V2 Hubs
Works with:
* [Samsung SmartPower Outlet](https://shop.smartthings.com/#!/products/smartpower-outlet)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Actuator** - represents that a Device has commands
* **Switch** - can detect state (possible values: on/off)
* **Refresh** - _refresh()_ command for status updates
* **Power Meter** - detects power meter for device in either w or kw.
* **Health Check** - indicates ability to get device health notifications
* **Sensor** - detects sensor events
## Device Health
SmartPower outlet with reporting interval of 5 mins
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __21min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
## 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 links
for the different models:
* [SmartPower Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/201084854-SmartPower-Outlet)
* [Samsung SmartThings Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957620)

View File

@@ -25,6 +25,9 @@ metadata {
capability "Sensor"
capability "Health Check"
// indicates that device keeps track of heartbeat (in state.heartbeat)
attribute "heartbeat", "string"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", deviceJoinName: "Outlet"
@@ -78,8 +81,10 @@ metadata {
def parse(String description) {
log.debug "description is $description"
// save heartbeat (i.e. last time we got a message from device)
state.heartbeat = Calendar.getInstance().getTimeInMillis()
def finalResult = zigbee.getKnownDescription(description)
def event = [:]
//TODO: Remove this after getKnownDescription can parse it automatically
if (!finalResult && description!="updated")
@@ -89,11 +94,10 @@ def parse(String description) {
log.info "final result = $finalResult"
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
event = null
}
else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10
event = createEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true)
sendEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true )
/*
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
power level is an integer. The exact power level with correct units needs to be handled in the device type
@@ -102,28 +106,13 @@ def parse(String description) {
}
else {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
event = createEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
}
}
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
event = createEvent(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]}"
event = null
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
}
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
}
return event
}
def off() {
@@ -133,24 +122,15 @@ def off() {
def on() {
zigbee.on()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.onOffRefresh()
}
def refresh() {
zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh()
sendEvent(name: "heartbeat", value: "alive", displayed:false)
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
}
def configure() {
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 10 * 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()
sendEvent(name: "checkInterval", value: 1200, displayed: false)
zigbee.onOffConfig() + powerConfig() + refresh()
}
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)

View File

@@ -86,7 +86,7 @@ metadata {
def parse(String description) {
log.debug "parse($description)"
def results = [:]
def results = null
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
// Ignore this in favor of orientation-based state

View File

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

View File

@@ -1,45 +0,0 @@
# Smartsense Moisture Sensor
Local Execution on V2 Hubs
Works with:
* [Samsung SmartThings Moisture Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-water-leak-sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Water Sensor** - can detect presence of water (dry or wet)
* **Health Check** - indicates ability to get device health notifications
## Device Health
SmartSense Moisture sensor with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
## Battery Specification
One CR2 3V battery required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the different sensors from SmartThings can be found in the following links
for the different models:
* [SmartSense Moisture Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202847044-SmartSense-Moisture-Sensor)
* [Samsung SmartThings Water Leak Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957630)
Other troubleshooting tips are listed as follows:
* [Troubleshooting: Samsung SmartThings Water Leak Sensor wont pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)

View File

@@ -13,8 +13,6 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
@@ -24,7 +22,6 @@ metadata {
capability "Temperature Measurement"
capability "Water Sensor"
capability "Health Check"
capability "Sensor"
command "enrollResponse"
@@ -102,7 +99,7 @@ def parse(String description) {
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : [:]
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
@@ -118,28 +115,14 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
@@ -149,8 +132,10 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
@@ -184,17 +169,50 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMoistureResult('dry')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMoistureResult('wet')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'dry with tamper alarm'
resultMap = getMoistureResult('dry')
break
case '0x0025': // Restore Report
log.debug 'water with tamper alarm'
resultMap = getMoistureResult('wet')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return Math.round(celsius)
return celsius
} else {
return Math.round(celsiusToFahrenheit(celsius))
return celsiusToFahrenheit(celsius) as Integer
}
}
@@ -202,37 +220,45 @@ private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [:]
def result = [
name: 'battery',
value: '--',
translatable: true
]
def volts = rawValue / 10
if (!(rawValue == 0 || rawValue == 255)) {
result.name = 'battery'
result.translatable = true
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28: 100, 27: 100, 26: 100, 25: 90, 24: 90, 23: 70,
22: 70, 21: 50, 20: 50, 19: 30, 18: 30, 17: 15, 16: 1, 15: 0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
result.value = pct
} else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
}
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
}
return result
@@ -255,8 +281,7 @@ private Map getTemperatureResult(value) {
name: 'temperature',
value: value,
descriptionText: descriptionText,
translatable: true,
unit: temperatureScale
translatable: true
]
}
@@ -275,13 +300,6 @@ private Map getMoistureResult(value) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
@@ -293,13 +311,23 @@ def refresh() {
}
def configure() {
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 7200, displayed: false)
// 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
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
}
def enrollResponse() {

View File

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

View File

@@ -1,47 +0,0 @@
# Smartsense Motion Sensor
Local Execution on V2 Hubs
Works with:
* [Samsung SmartThings Motion Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-motion-sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Motion Sensor** - can detect motion
* **Battery** - defines device uses a battery
* **Refresh** - _refresh()_ command for status updates
* **Health Check** - indicates ability to get device health notifications
## Device Health
SmartSense Motion sensor with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
## Battery Specification
One CR2477 (for Samsung SmartThings Motion Sensor) / CR123A (SmartSense Motion Sensor) 3V battery is required.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
for the different models:
* [SmartSense Motion Sensor (original model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903280-SmartSense-Motion-Sensor-original-model-)
* [SmartSense Motion Sensor (2014 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203077520-SmartSense-Motion-Sensor-2014-model-)
* [Samsung SmartThings Motion Sensor (2015 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957580-Samsung-SmartThings-Motion-Sensor-2015-model-)
Other troubleshooting tips are listed as follows:
* [Troubleshooting: Samsung SmartThings Motion Sensor is stuck showing "Motion Detected" or "No Motion"](https://support.smartthings.com/hc/en-us/articles/200961130-Troubleshooting-Samsung-SmartThings-Motion-Sensor-is-stuck-showing-Motion-Detected-or-No-Motion-)
* [Troubleshooting: Samsung SmartThings Motion Sensor wont pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)

View File

@@ -13,8 +13,6 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -24,7 +22,6 @@ metadata {
capability "Temperature Measurement"
capability "Refresh"
capability "Health Check"
capability "Sensor"
command "enrollResponse"
@@ -106,7 +103,7 @@ def parse(String description) {
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : [:]
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
@@ -122,37 +119,19 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
case 0x0406:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
log.debug 'motion'
resultMap.name = 'motion'
}
log.debug 'motion'
resultMap.name = 'motion'
break
}
}
@@ -162,8 +141,10 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
@@ -201,18 +182,52 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMotionResult('inactive')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMotionResult('active')
break
case '0x0022': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap = getMotionResult('active')
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap = getMotionResult('inactive')
break
case '0x0025': // Restore Report
break
case '0x0026': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap = getMotionResult('active')
break
case '0x0028': // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return Math.round(celsius)
return celsius
} else {
return Math.round(celsiusToFahrenheit(celsius))
return celsiusToFahrenheit(celsius) as Integer
}
}
@@ -220,35 +235,45 @@ private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [:]
def result = [
name: 'battery',
value: '--',
translatable: true
]
def volts = rawValue / 10
if (!(rawValue == 0 || rawValue == 255)) {
result.name = 'battery'
result.translatable = true
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28: 100, 27: 100, 26: 100, 25: 90, 24: 90, 23: 70,
22: 70, 21: 50, 20: 50, 19: 30, 18: 30, 17: 15, 16: 1, 15: 0]
def minVolts = 15
def maxVolts = 28
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
}
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
result.value = pct
} else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
def value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
}
@@ -272,8 +297,7 @@ private Map getTemperatureResult(value) {
name: 'temperature',
value: value,
descriptionText: descriptionText,
translatable: true,
unit: temperatureScale
translatable: true
]
}
@@ -288,13 +312,6 @@ private Map getMotionResult(value) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def refresh() {
log.debug "refresh called"
def refreshCmds = [
@@ -306,13 +323,24 @@ def refresh() {
}
def configure() {
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 7200, displayed: false)
// 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
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
}
def enrollResponse() {

View File

@@ -0,0 +1,344 @@
/**
* SmartSense Motion/Temp Sensor
*
* Copyright 2014 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Motion Sensor"
capability "Configuration"
capability "Battery"
capability "Temperature Measurement"
capability "Refresh"
capability "Sensor"
command "enrollResponse"
}
simulator {
status "active": "zone report :: type: 19 value: 0031"
status "inactive": "zone report :: type: 19 value: 0030"
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {
multiAttributeTile(name:"motion", type: "generic", width: 6, height: 4){
tileAttribute ("device.motion", key: "PRIMARY_CONTROL") {
attributeState "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
attributeState "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
}
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["motion", "temperature"])
details(["motion", "temperature", "battery", "refresh"])
}
}
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
case 0x0406:
log.debug 'motion'
resultMap.name = 'motion'
break
}
}
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
resultMap = getTemperatureResult(value)
}
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
def value = descMap.value.endsWith("01") ? "active" : "inactive"
resultMap = getMotionResult(value)
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
resultMap = getTemperatureResult(value)
}
return resultMap
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMotionResult('inactive')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMotionResult('active')
break
case '0x0022': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap = getMotionResult('active')
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap = getMotionResult('inactive')
break
case '0x0025': // Restore Report
break
case '0x0026': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap = getMotionResult('active')
break
case '0x0028': // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return celsius
} else {
return celsiusToFahrenheit(celsius) as Integer
}
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
log.debug rawValue
def result = [
name: 'battery',
value: '--'
]
def volts = rawValue / 10
def descriptionText
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else if (volts > 0){
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
return result
}
private Map getTemperatureResult(value) {
log.debug 'TEMP'
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
}
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
return [
name: 'temperature',
value: value,
descriptionText: descriptionText
]
}
private Map getMotionResult(value) {
log.debug 'motion'
String linkText = getLinkText(device)
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
return [
name: 'motion',
value: value,
descriptionText: descriptionText
]
}
def refresh() {
log.debug "refresh called"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
]
return refreshCmds + enrollResponse()
}
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
}
def enrollResponse() {
log.debug "Sending enroll response"
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
[
//Resending the CIE in case the enroll request is sent before CIE is written
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}

View File

@@ -44,7 +44,7 @@ metadata {
}
def parse(String description) {
def results = [:]
def results
if (isZoneType19(description) || !isSupportedDescription(description)) {
results = parseBasicMessage(description)
}
@@ -57,25 +57,21 @@ def parse(String description) {
private Map parseBasicMessage(description) {
def name = parseName(description)
if (name != null) {
def value = parseValue(description)
def linkText = getLinkText(device)
def descriptionText = parseDescriptionText(linkText, value, description)
def handlerName = value
def isStateChange = isStateChange(device, name, value)
def value = parseValue(description)
def linkText = getLinkText(device)
def descriptionText = parseDescriptionText(linkText, value, description)
def handlerName = value
def isStateChange = isStateChange(device, name, value)
def results = [
name : name,
value : value,
linkText : linkText,
descriptionText: descriptionText,
handlerName : handlerName,
isStateChange : isStateChange,
displayed : displayed(description, isStateChange)
]
} else {
results = [:]
}
def results = [
name: name,
value: value,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
]
log.debug "Parse returned $results.descriptionText"
return results
}

View File

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

View File

@@ -1,46 +0,0 @@
# Smartsense Multi Sensor
Local Execution on V2 Hubs
Works with:
* [Samsung SmartThings Multi Sensor](https://shop.smartthings.com/#!/products/smartsense-multi)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Three Axis** - monitors the state of a single axis
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Sensor** - detects sensor events
* **Contact Sensor** - can detect contact (possible values: open,closed)
* **Acceleration Sensor** - allows for acceleration detection.
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Health Check** - indicates ability to get device health notifications
## Device Health
SmartSense Multi sensor with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
## Battery Specification
One CR2450 (for Samsung SmartThings Multipurpose Sensor) battery / Two AAAA (for SmartSense Multi Sensor) batteries required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Other troubleshooting tips are listed as follows:
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor is stuck on "open" or "closed"](https://support.smartthings.com/hc/en-us/articles/200955940-Troubleshooting-Samsung-SmartThings-Multipurpose-Sensor-is-stuck-on-open-or-closed-)
* [Troubleshooting: Temperature reading for the Samsung SmartThings Multipurpose Sensor is off](https://support.smartthings.com/hc/en-us/articles/200756845-Troubleshooting-Temperature-reading-for-the-Samsung-SmartThings-Multipurpose-Sensor-is-off)
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor wont pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)

View File

@@ -13,7 +13,6 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -127,7 +126,7 @@ def parse(String description) {
map = parseIasMessage(description)
}
def result = map ? createEvent(map) : [:]
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
@@ -147,33 +146,20 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
resultMap = getBatteryResult(cluster.data.last())
break
case 0xFC02:
log.debug 'ACCELERATION'
log.debug 'ACCELERATION'
break
case 0x0402:
if (cluster.command == 0x07) {
if(cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break
log.debug 'TEMP'
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
@@ -182,8 +168,10 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
@@ -236,13 +224,47 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
if (garageSensor != "Yes"){
resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
resultMap = getContactResult('closed')
}
break
case '0x0021': // Open/Motion/Wet
if (garageSensor != "Yes"){
resultMap = getContactResult('open')
}
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
if (garageSensor != "Yes"){
resultMap = getContactResult('closed')
}
break
case '0x0025': // Restore Report
if (garageSensor != "Yes"){
resultMap = getContactResult('open')
}
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
}
@@ -272,44 +294,53 @@ def updated() {
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return Math.round(celsius)
return celsius
} else {
return Math.round(celsiusToFahrenheit(celsius))
return celsiusToFahrenheit(celsius) as Integer
}
}
private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
def result = [:]
def result = [
name: 'battery',
value: '--',
translatable: true
]
def volts = rawValue / 10
if (!(rawValue == 0 || rawValue == 255)) {
result.name = 'battery'
result.translatable = true
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28: 100, 27: 100, 26: 100, 25: 90, 24: 90, 23: 70,
22: 70, 21: 50, 20: 50, 19: 30, 18: 30, 17: 15, 16: 1, 15: 0]
def minVolts = 15
def maxVolts = 28
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
}
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
result.value = pct
} else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
}
@@ -327,11 +358,10 @@ private Map getTemperatureResult(value) {
'{{ device.displayName }} was {{ value }}°F'
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
translatable: true,
unit: temperatureScale
name: 'temperature',
value: value,
descriptionText: descriptionText,
translatable: true
]
}
@@ -366,13 +396,6 @@ private getAccelerationResult(numValue) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def refresh() {
log.debug "Refreshing Values "
@@ -380,42 +403,80 @@ def refresh() {
if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings "
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
Separating these out in a separate if-else because I do not want to touch Centralite part
as of now.
*/
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
refreshCmds = refreshCmds + [
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
Separating these out in a separate if-else because I do not want to touch Centralite part
as of now.
*/
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
} else {
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
refreshCmds = refreshCmds + [
/* sensitivity - default value (8) */
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
}
//Common refresh commands
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
zigbee.readAttribute(0x0001, 0x0020) +
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
refreshCmds = refreshCmds + [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
return refreshCmds + enrollResponse()
}
def configure() {
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting"
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
def configCmds = zigbee.batteryConfig() +
zigbee.temperatureConfig(30, 300) +
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
return refresh() + configCmds
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return configCmds + refresh()
}
private getEndpointId() {

View File

@@ -0,0 +1,363 @@
/**
* SmartSense Open/Closed Accelerometer Sensor
*
* Copyright 2014 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery"
capability "Configuration"
capability "Contact Sensor"
capability "Acceleration Sensor"
capability "Refresh"
capability "Temperature Measurement"
capability "Health Check"
command "enrollResponse"
}
simulator {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
}
}
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
state("inactive", label:'${name}', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
}
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
state "temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main (["contact", "acceleration", "temperature"])
details(["contact", "acceleration", "temperature", "battery", "refresh"])
}
}
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
case 0xFC02:
break
case 0x0402:
log.debug 'TEMP'
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private int getHumidity(value) {
return Math.round(Double.parseDouble(value))
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
resultMap = getTemperatureResult(value)
}
else if (descMap.cluster == "FC02" && descMap.attrId == "0002") {
Integer.parseInt(descMap.value,8)
}
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
resultMap = getTemperatureResult(value)
}
return resultMap
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getContactResult('closed')
break
case '0x0021': // Open/Motion/Wet
resultMap = getContactResult('open')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
resultMap = getContactResult('closed')
break
case '0x0025': // Restore Report
resultMap = getContactResult('open')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return celsius
} else {
return celsiusToFahrenheit(celsius) as Integer
}
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def volts = rawValue / 10
def descriptionText
if (rawValue == 0 || rawValue == 255) {}
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
return result
}
private Map getTemperatureResult(value) {
log.debug 'TEMP'
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
}
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
return [
name: 'temperature',
value: value,
descriptionText: descriptionText
]
}
private Map getContactResult(value) {
log.debug 'Contact Status'
def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
return [
name: 'contact',
value: value,
descriptionText: descriptionText
]
}
private getAccelerationResult(numValue) {
def name = "acceleration"
def value = numValue.endsWith("1") ? "active" : "inactive"
//def linkText = getLinkText(device)
def descriptionText = "$linkText was $value"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
//unit: null,
//linkText: linkText,
descriptionText: descriptionText,
//handlerName: value,
isStateChange: isStateChange
// displayed: displayed(description, isStateChange)
]
}
def refresh() {
log.debug "Refreshing Temperature and Battery "
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
//"st rattr 0x${device.deviceNetworkId} 1 0xFC02 2", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
]
return refreshCmds + enrollResponse()
}
def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0xFC02 2 0x18 30 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
}
def enrollResponse() {
log.debug "Sending enroll response"
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
[
//Resending the CIE in case the enroll request is sent before CIE is written
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}

View File

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

View File

@@ -1,42 +0,0 @@
# Smartsense Open/Closed Sensor
Local Execution on V2 Hubs
Works with:
* [Samsung SmartThings Open/Closed Sensor](https://shop.smartthings.com/#!/packs/smartsense-open-closed-sensor/)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Contact Sensor** - can detect contact (possible values: open,closed)
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Health Check** - indicates ability to get device health notifications
* **Sensor** - detects sensor events
## Device Health
SmartSense Open Closed sensor with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
## Battery Specification
One CR2 3V battery required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
* [SmartSense Open/Closed Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202836844-SmartSense-Open-Closed-Sensor)

View File

@@ -13,8 +13,7 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery"
@@ -22,26 +21,24 @@ metadata {
capability "Contact Sensor"
capability "Refresh"
capability "Temperature Measurement"
capability "Health Check"
capability "Sensor"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
}
simulator {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
@@ -74,10 +71,10 @@ metadata {
details(["contact","temperature","battery","refresh"])
}
}
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
@@ -91,10 +88,10 @@ def parse(String description) {
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : [:]
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
@@ -102,35 +99,22 @@ def parse(String description) {
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
if (cluster.command == 0x07){
if (cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
log.debug 'TEMP'
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
@@ -140,8 +124,10 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
boolean ignoredMessage = cluster.profileId != 0x0104 ||
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
@@ -149,14 +135,14 @@ private boolean shouldProcessMessage(cluster) {
private int getHumidity(value) {
return Math.round(Double.parseDouble(value))
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
@@ -165,10 +151,10 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
@@ -179,10 +165,42 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
}
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getContactResult('closed')
break
case '0x0021': // Open/Motion/Wet
resultMap = getContactResult('open')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
resultMap = getContactResult('closed')
break
case '0x0025': // Restore Report
resultMap = getContactResult('open')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
@@ -195,20 +213,23 @@ def getTemperature(value) {
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [:]
def result = [
name: 'battery'
]
def volts = rawValue / 10
if (!(rawValue == 0 || rawValue == 255)) {
def descriptionText
if (rawValue == 0 || rawValue == 255) {}
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
result.name = 'battery'
}
return result
@@ -226,8 +247,7 @@ private Map getTemperatureResult(value) {
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
descriptionText: descriptionText
]
}
@@ -242,13 +262,6 @@ private Map getContactResult(value) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
@@ -260,15 +273,22 @@ def refresh() {
}
def configure() {
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
// 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
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
}
def enrollResponse() {

View File

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

View File

@@ -1,42 +0,0 @@
# SmartSense Temp/Humidity Sensor
Local Execution on V2 Hubs
Works with:
* [Samsung SmartSense Temp/Humidity Sensor](https://shop.smartthings.com/#!/products/smartsense-temp-humidity-sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Relative Humidity Measurement** - defines device measures relative humidity
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Health Check** - indicates ability to get device health notifications
* **Sensor** - detects sensor events
## Device Health
SmartSense Temp/Humidity Sensor with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
* HubV2 AppEngine 1.5.1 - __12min__ checkIntervalr 5 min interval is confirmed
## Battery Specification
One CR2 battery is required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
* [Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203040294)

View File

@@ -21,7 +21,6 @@ metadata {
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Health Check"
capability "Sensor"
fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
}
@@ -84,7 +83,7 @@ def parse(String description) {
}
log.debug "Parse returned $map"
return map ? createEvent(map) : [:]
return map ? createEvent(map) : null
}
private Map parseCatchAllMessage(String description) {
@@ -93,37 +92,20 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
case 0xFC45:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
resultMap = getHumidityResult(display)
}
String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
resultMap = getHumidityResult(display)
break
}
}
@@ -133,8 +115,10 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
@@ -207,20 +191,22 @@ private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [:]
def result = [
name: 'battery'
]
def volts = rawValue / 10
if (!(rawValue == 0 || rawValue == 255)) {
def descriptionText
if (rawValue == 0 || rawValue == 255) {}
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
result.name = 'battery'
}
return result
@@ -238,46 +224,51 @@ private Map getTemperatureResult(value) {
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
descriptionText: descriptionText
]
}
private Map getHumidityResult(value) {
log.debug 'Humidity'
return value ? [name: 'humidity', value: value, unit: '%'] : [:]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
return [
name: 'humidity',
value: value,
unit: '%'
]
}
def refresh()
{
log.debug "refresh temperature, humidity, and battery"
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
zigbee.readAttribute(0x0402, 0x0000) +
zigbee.readAttribute(0x0001, 0x0020)
[
"zcl mfg-code 0xC2DF", "delay 1000",
"zcl global read 0xFC45 0", "delay 1000",
"send 0x${device.deviceNetworkId} 1 1", "delay 1000",
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
]
}
def configure() {
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 7200, displayed: false)
log.debug "Configuring Reporting and Bindings."
def humidityConfigCmds = [
def configCmds = [
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return refresh() + humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
return configCmds + refresh() // send refresh cmds as part of config
}
private hex(value) {

View File

@@ -16,8 +16,6 @@
metadata {
definition (name: "Simulated Alarm", namespace: "smartthings/testing", author: "SmartThings") {
capability "Alarm"
capability "Sensor"
capability "Actuator"
}
simulator {

View File

@@ -1,8 +1,6 @@
metadata {
definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") {
capability "Color Control"
capability "Sensor"
capability "Actuator"
}
simulator {

View File

@@ -15,7 +15,6 @@ metadata {
// Automatically generated. Make future change here.
definition (name: "Simulated Contact Sensor", namespace: "smartthings/testing", author: "bob") {
capability "Contact Sensor"
capability "Sensor"
command "open"
command "close"

View File

@@ -15,8 +15,6 @@ metadata {
// Automatically generated. Make future change here.
definition (name: "Simulated Lock", namespace: "smartthings/testing", author: "bob") {
capability "Lock"
capability "Sensor"
capability "Actuator"
}
// Simulated lock

View File

@@ -15,7 +15,6 @@ metadata {
// Automatically generated. Make future change here.
definition (name: "Simulated Motion Sensor", namespace: "smartthings/testing", author: "bob") {
capability "Motion Sensor"
capability "Sensor"
command "active"
command "inactive"

View File

@@ -15,7 +15,6 @@ metadata {
// Automatically generated. Make future change here.
definition (name: "Simulated Presence Sensor", namespace: "smartthings/testing", author: "bob") {
capability "Presence Sensor"
capability "Sensor"
command "arrived"
command "departed"

View File

@@ -16,8 +16,6 @@ metadata {
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
capability "Switch"
capability "Relay Switch"
capability "Sensor"
capability "Actuator"
command "onPhysical"
command "offPhysical"

View File

@@ -16,7 +16,6 @@ metadata {
definition (name: "Simulated Temperature Sensor", namespace: "smartthings/testing", author: "SmartThings") {
capability "Temperature Measurement"
capability "Switch Level"
capability "Sensor"
command "up"
command "down"

View File

@@ -16,8 +16,6 @@ metadata {
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
capability "Thermostat"
capability "Relative Humidity Measurement"
capability "Sensor"
capability "Actuator"
command "tempUp"
command "tempDown"

View File

@@ -15,7 +15,6 @@ metadata {
// Automatically generated. Make future change here.
definition (name: "Simulated Water Sensor", namespace: "smartthings/testing", author: "SmartThings") {
capability "Water Sensor"
capability "Sensor"
command "wet"
command "dry"

View File

@@ -1,42 +0,0 @@
# Device Tiles Examples and Reference
This package contains examples of Device tiles, organized by tile type.
## Purpose
Each Device Handler shows example usages of a specific tile, and is meant to represent the variety of permutations that a tile can be configured.
The various tiles can be used by QA to test tiles on all supported mobile devices, and by developers as a reference implementation.
## Installation
1. Self-publish the Device Handlers in this package.
2. Self-publish the Device Tile Controller SmartApp. The SmartApp can be found [here](https://github.com/SmartThingsCommunity/SmartThingsPublic/blob/master/smartapps/smartthings/tile-ux/device-tile-controller.src/device-tile-controller.groovy).
3. Install the SmartApp from the Marketplace, under "My Apps".
4. Select the simulated devices you want to install and press "Done".
The simulated devices can then be found in the "Things" view of "My Home" in the mobile app.
You may wish to create a new room for these simulated devices for easy access.
## Usage
Each simulated device can be interacted with like other devices.
You can use the mobile app to interact with the tiles to see how they look and behave.
## Troubleshooting
If you get an error when installing the simulated devices using the controller SmartApp, ensure that you have published all the Device Handlers for yourself.
Also check live logging to see if there is a specific tile that is causing installation issues.
## FAQ
*Question: A tile isn't behaving as expected. What should I do?*
QA should create a JIRA ticket for any issues or inconsistencies of tiles across devices.
Developers may file a support ticket, and reference the specific tile and issue observed.
*Question: I'd like to contribute an example tile usage that would be helpful for testing and reference purposes. Can I do that?*
We recommend that you open an issue in the SmartThingsPublic repository describing the example tile and usage.
That way we can discuss with you the proposed change, and then if appropriate you can create a PR associated to the issue.

View File

@@ -22,7 +22,7 @@ metadata {
tiles(scale: 2) {
valueTile("currentColor", "device.color") {
state "color", label: '${currentValue}', defaultState: true
state "default", label: '${currentValue}'
}
controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) {
@@ -41,13 +41,6 @@ def parse(String description) {
log.debug "Parsing '${description}'"
}
def setColor(value) {
log.debug "setting color: $value"
if (value.hex) { sendEvent(name: "color", value: value.hex) }
if (value.hue) { sendEvent(name: "hue", value: value.hue) }
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation) }
}
def setSaturation(percent) {
log.debug "Executing 'setSaturation'"
sendEvent(name: "saturation", value: percent)

View File

@@ -39,7 +39,7 @@ metadata {
}
valueTile("rangeValue", "device.rangedLevel", height: 2, width: 2) {
state "range", label:'${currentValue}', defaultState: true
state "default", label:'${currentValue}'
}
controlTile("rangeSliderConstrained", "device.rangedLevel", "slider", height: 2, width: 4, range: "(40..60)") {

View File

@@ -41,17 +41,17 @@ metadata {
// standard flat tile with only a label
standardTile("flatLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
state "label", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff", defaultState: true
state "default", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff"
}
// standard flat tile with icon and label
standardTile("flatIconLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
state "iconLabel", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff", defaultState: true
state "default", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff"
}
// standard flat tile with only icon (Refreh text is IN the icon file)
standardTile("flatIcon", "device.switch", width: 2, height: 2, decoration: "flat") {
state "icon", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
// standard with defaultState = true
@@ -74,19 +74,19 @@ metadata {
// utility tiles to fill the spaces
standardTile("empty2x2", "null", width: 2, height: 2, decoration: "flat") {
state "emptySmall", label:'', defaultState: true
state "default", label:''
}
standardTile("empty4x2", "null", width: 4, height: 2, decoration: "flat") {
state "emptyBigger", label:'', defaultState: true
state "default", label:''
}
// multi-line text (explicit newlines)
standardTile("multiLine", "device.multiLine", width: 2, height: 2) {
state "multiLine", label: '${currentValue}', defaultState: true
state "default", label: '${currentValue}'
}
standardTile("multiLineWithIcon", "device.multiLine", width: 2, height: 2) {
state "multiLineIcon", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true
state "default", label: '${currentValue}', icon: "st.switches.switch.off"
}
main("actionRings")

View File

@@ -22,68 +22,68 @@ metadata {
tiles(scale: 2) {
valueTile("text", "device.text", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true
state "default", label:'${currentValue}'
}
valueTile("longText", "device.longText", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true
state "default", label:'${currentValue}'
}
valueTile("integer", "device.integer", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true
state "default", label:'${currentValue}'
}
valueTile("integerFloat", "device.integerFloat", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true
state "default", label:'${currentValue}'
}
valueTile("pi", "device.pi", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true
state "default", label:'${currentValue}'
}
valueTile("floatAsText", "device.floatAsText", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true
state "default", label:'${currentValue}'
}
valueTile("bgColor", "device.integer", width: 2, height: 2) {
state "val", label:'${currentValue}', backgroundColor: "#e86d13", defaultState: true
state "default", label:'${currentValue}', backgroundColor: "#e86d13"
}
valueTile("bgColorRange", "device.integer", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
state "default", label:'${currentValue}', backgroundColors: [
[value: 10, color: "#ff0000"],
[value: 90, color: "#0000ff"]
]
}
valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
state "default", label:'${currentValue}', backgroundColors: [
[value: 10, color: "#333333"]
]
}
valueTile("bgColorRangeConflict", "device.integer", width: 2, height: 2) {
state "valWithConflict", label:'${currentValue}', defaultState: true, backgroundColors: [
state "default", label:'${currentValue}', backgroundColors: [
[value: 10, color: "#990000"],
[value: 10, color: "#000099"]
]
}
valueTile("noValue", "device.nada", width: 4, height: 2) {
state "noval", label:'${currentValue}', defaultState: true
state "default", label:'${currentValue}'
}
valueTile("multiLine", "device.multiLine", width: 3, height: 2) {
state "val", label: '${currentValue}', defaultState: true
state "default", label: '${currentValue}'
}
valueTile("multiLineWithIcon", "device.multiLine", width: 3, height: 2) {
state "val", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true
state "default", label: '${currentValue}', icon: "st.switches.switch.off"
}
main("text")
details([
"text", "longText", "integer",
"text", "longText", "integer",
"integerFloat", "pi", "floatAsText",
"bgColor", "bgColorRange", "bgColorRangeSingleItem",
"bgColorRangeConflict", "noValue",

View File

@@ -39,15 +39,15 @@ metadata {
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", icon: 'st.Weather.weather1', action:"randomizeLevel", defaultState: true
attributeState "default", icon: 'st.Weather.weather1', action:"randomizeLevel"
}
tileAttribute("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", defaultState: true
attributeState "default", action:"switch level.setLevel"
}
}
multiAttributeTile(name:"valueTile", type:"generic", width:6, height:4) {
tileAttribute("device.level", key: "PRIMARY_CONTROL") {
attributeState "level", label:'${currentValue}', defaultState: true, backgroundColors:[
attributeState "default", label:'${currentValue}', backgroundColors:[
[value: 0, color: "#ff0000"],
[value: 20, color: "#ffff00"],
[value: 40, color: "#00ff00"],
@@ -69,34 +69,34 @@ metadata {
}
multiAttributeTile(name:"lengthyTile", type:"generic", width:6, height:4) {
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821"
}
tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") {
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821"
}
}
multiAttributeTile(name:"multilineTile", type:"generic", width:6, height:4) {
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821"
}
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821"
}
}
multiAttributeTile(name:"lengthyTileWithIcon", type:"generic", width:6, height:4) {
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on"
}
tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") {
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on"
}
}
multiAttributeTile(name:"multilineTileWithIcon", type:"generic", width:6, height:4) {
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on"
}
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on"
}
}

View File

@@ -96,10 +96,10 @@ metadata {
}
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "reset", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single", defaultState: true
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "refresh", label:"", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
@@ -173,6 +173,7 @@ def setColor(value) {
def reset() {
log.debug "Executing 'reset'"
setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23])
//parent.poll()
}
def setAdjustedColor(value) {
@@ -188,6 +189,7 @@ def setAdjustedColor(value) {
def refresh() {
log.debug "Executing 'refresh'"
//parent.manualRefresh()
}
def adjustOutgoingHue(percent) {
@@ -206,3 +208,4 @@ def adjustOutgoingHue(percent) {
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}

View File

@@ -37,10 +37,10 @@ metadata {
attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing")
}
tileAttribute("device.status", key: "PREVIOUS_TRACK") {
attributeState("status", action:"music Player.previousTrack", defaultState: true)
attributeState("default", action:"music Player.previousTrack")
}
tileAttribute("device.status", key: "NEXT_TRACK") {
attributeState("status", action:"music Player.nextTrack", defaultState: true)
attributeState("default", action:"music Player.nextTrack")
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState("level", action:"music Player.setLevel")
@@ -50,7 +50,7 @@ metadata {
attributeState("muted", action:"music Player.unmute", nextState: "unmuted")
}
tileAttribute("device.trackDescription", key: "MARQUEE") {
attributeState("trackDescription", label:"${currentValue}", defaultState: true)
attributeState("default", label:"${currentValue}")
}
}

View File

@@ -32,14 +32,14 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true)
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "tempUp")
attributeState("VALUE_DOWN", action: "tempDown")
}
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
attributeState("humidity", label:'${currentValue}%', unit:"%", defaultState: true)
attributeState("default", label:'${currentValue}%', unit:"%")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
@@ -53,16 +53,15 @@ metadata {
attributeState("auto", label:'${name}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
attributeState("default", label:'${currentValue}', unit:"dF")
}
}
multiAttributeTile(name:"thermostatNoHumidity", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
attributeState("temp", label:'${currentValue}', unit:"dF")
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "tempUp")
@@ -80,16 +79,15 @@ metadata {
attributeState("auto", label:'${name}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF")
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
attributeState("default", label:'${currentValue}', unit:"dF")
}
}
multiAttributeTile(name:"thermostatBasic", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true,
attributeState("default", label:'${currentValue}', unit:"dF",
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
@@ -120,30 +118,30 @@ metadata {
)
}
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "tempDown", label:'down', action:"tempDown", defaultState: true
state "default", label:'down', action:"tempDown"
}
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "tempUp", label:'up', action:"tempUp", defaultState: true
state "default", label:'up', action:"tempUp"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
}
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "heatDown", label:'down', action:"heatDown", defaultState: true
state "default", label:'down', action:"heatDown"
}
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "heatUp", label:'up', action:"heatUp", defaultState: true
state "default", label:'up', action:"heatUp"
}
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
}
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "coolDown", label:'down', action:"coolDown", defaultState: true
state "default", label:'down', action:"coolDown"
}
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "coolUp", label:'up', action:"coolUp", defaultState: true
state "default", label:'up', action:"coolUp"
}
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {

View File

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

View File

@@ -1,43 +0,0 @@
# Tyco Door Window Sensor
Cloud Execution
Works with:
* [Tyco Door Window Sensor](https://support.smartthings.com/hc/en-us/articles/204834100-Tyco-Door-Window-Sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Battery** - defines device uses a battery
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Contact Sensor** - can detect contact (open/close)
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - can measure the device temperature
* **Health Check** - indicates ability to get device health notifications
## Device Health
Tyco Door Window Sensor with reporting interval of 5 mins.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
* __12min__ checkInterval
## Battery Specification
3V CR2032 battery is required.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that either the sensor needs to be reseted or the sensor is out of range.
Reset needs to be done by inserting the battery in the sensor and then quickly pressing the adjacent black button 10 times. Pairing should be tried again now.
It may happen that sensor is out of range, then pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
for the different models:
* [Tyco Door Window Sensor (MCT-340)](https://support.smartthings.com/hc/en-us/articles/204834100-Tyco-Door-Window-Sensor)

View File

@@ -13,8 +13,7 @@
* for the specific language governing permissions and limitations under the License.
*
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery"
@@ -22,29 +21,28 @@ metadata {
capability "Contact Sensor"
capability "Refresh"
capability "Temperature Measurement"
capability "Health Check"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "Visonic", model: "MCT-340 SMA"
}
simulator {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles {
standardTile("contact", "device.contact", width: 2, height: 2) {
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
}
valueTile("temperature", "device.temperature", inactiveLabel: false) {
state "temperature", label:'${currentValue}°',
backgroundColors:[
@@ -60,23 +58,23 @@ metadata {
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
main (["contact", "temperature"])
details(["contact","temperature","battery","refresh","configure"])
}
}
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
@@ -90,10 +88,10 @@ def parse(String description) {
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
@@ -101,7 +99,7 @@ def parse(String description) {
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
@@ -127,20 +125,20 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
@@ -149,10 +147,10 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
@@ -163,11 +161,42 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getContactResult('closed')
break
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
case '0x0021': // Open/Motion/Wet
resultMap = getContactResult('open')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
resultMap = getContactResult('closed')
break
case '0x0025': // Restore Report
resultMap = getContactResult('open')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
@@ -180,18 +209,22 @@ def getTemperature(value) {
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [:]
if (!(rawValue == 0 || rawValue == 255)) {
def volts = rawValue / 10
def result = [
name: 'battery'
]
def volts = rawValue / 10
def descriptionText
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
result.name = 'battery'
}
return result
@@ -209,8 +242,7 @@ private Map getTemperatureResult(value) {
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
descriptionText: descriptionText
]
}
@@ -225,51 +257,53 @@ private Map getContactResult(value) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x0402, 0x0000) // Read the Temperature Cluster
}
def refresh()
{
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
[
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
]
return refreshCmds + enrollResponse()
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [
def configCmds = [
"delay 1000",
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
//"raw 0x500 {01 23 00 00 00}", "delay 200",
//"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}",
"delay 500"
]
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
return configCmds + enrollResponse() + refresh() // send refresh cmds as part of config
}
def enrollResponse() {
log.debug "Sending enroll response"
[
[
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1"
]
}
private hex(value) {

View File

@@ -1,7 +1,8 @@
/**
* ZigBee Button
* Iris Smart Fob
*
* Copyright 2015 Mitch Pond
* Presence code adapted from SmartThings Arrival Sensor HA device type
*
* 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:
@@ -13,229 +14,181 @@
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") {
capability "Actuator"
capability "Battery"
capability "Button"
definition (name: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") {
capability "Battery"
capability "Button"
capability "Configuration"
capability "Refresh"
capability "Sensor"
command "enrollResponse"
fingerprint inClusters: "0000, 0001, 0003, 0020, 0402, 0B05", outClusters: "0003, 0006, 0008, 0019", manufacturer: "OSRAM", model: "LIGHTIFY Dimming Switch", deviceJoinName: "OSRAM LIGHTIFY Dimming Switch"
//fingerprint inClusters: "0000, 0001, 0003, 0020, 0500", outClusters: "0003,0019", manufacturer: "CentraLite", model: "3455-L", deviceJoinName: "Iris Care Pendant"
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button"
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob"
capability "Presence Sensor"
capability "Sensor"
//fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0001,0003,0007,0020,0B05", outClusters: "0003,0006,0019", model:"3450-L", manufacturer: "CentraLite"
}
preferences{
input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"",
defaultValue: 3, displayDuringSetup: false)
input "checkInterval", "enum", title: "Presence timeout (minutes)",
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
input "logging", "bool", title: "Enable debug logging",
defaultValue: false, displayDuringSetup: false
}
simulator {}
preferences {
section {
input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"", defaultValue: 1, displayDuringSetup: false)
tiles(scale: 2) {
standardTile("presence", "device.presence", width: 4, height: 4, canChangeBackground: true) {
state "present", label: "Present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff"
}
}
tiles {
standardTile("button", "device.button", width: 2, height: 2) {
state "default", label: "", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
state "button 1 pushed", label: "pushed #1", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#79b821"
standardTile("button", "device.button", decoration: "flat", width: 2, height: 2) {
state "default", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
}
valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main (["button"])
details(["button", "battery", "refresh"])
}
main (["presence"])
details(["presence","button","battery"])
}
}
def parse(String description) {
log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) {
sendEvent(event)
}
else {
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
def descMap = zigbee.parseDescriptionAsMap(description)
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
event = getBatteryResult(zigbee.convertHexToInt(descMap.value))
}
else if (descMap.clusterInt == 0x0006 || descMap.clusterInt == 0x0008) {
event = parseNonIasButtonMessage(descMap)
}
}
else if (description?.startsWith('zone status')) {
event = parseIasButtonMessage(description)
}
log.debug "Parse returned $event"
def result = event ? createEvent(event) : []
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
}
private Map parseIasButtonMessage(String description) {
def zs = zigbee.parseZoneStatus(description)
return zs.isAlarm2Set() ? getButtonResult("press") : getButtonResult("release")
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def volts = rawValue / 10
if (volts > 3.0 || volts == 0 || rawValue == 0xFF) {
return [:]
}
else {
def result = [
name: 'battery'
]
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def linkText = getLinkText(device)
result.descriptionText = "${linkText} battery was ${result.value}%"
return result
}
}
private Map parseNonIasButtonMessage(Map descMap){
def buttonState = ""
def buttonNumber = 0
if (((device.getDataValue("model") == "3460-L") || (device.getDataValue("model") == "3450-L"))
&&(descMap.clusterInt == 0x0006)) {
if (descMap.command == "01") {
getButtonResult("press")
}
else if (descMap.command == "00") {
getButtonResult("release")
}
}
else if (descMap.clusterInt == 0x0006) {
buttonState = "pushed"
if (descMap.command == "01") {
buttonNumber = 1
}
else if (descMap.command == "00") {
buttonNumber = 2
}
if (buttonNumber !=0) {
def descriptionText = "$device.displayName button $buttonNumber was $buttonState"
return createEvent(name: "button", value: buttonState, data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
}
else {
return [:]
}
}
else if (descMap.clusterInt == 0x0008) {
if (descMap.command == "05") {
state.buttonNumber = 1
getButtonResult("press", 1)
}
else if (descMap.command == "01") {
state.buttonNumber = 2
getButtonResult("press", 2)
}
else if (descMap.command == "03") {
getButtonResult("release", state.buttonNumber)
}
}
}
def refresh() {
log.debug "Refreshing Battery"
return zigbee.readAttribute(0x0001, 0x20) +
zigbee.enrollResponse()
}
def configure() {
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def cmds = []
if (device.getDataValue("model") == "3450-L") {
cmds << [
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 300",
"zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 300",
"zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 300",
"zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 300"
]
}
return zigbee.onOffConfig() +
zigbee.levelConfig() +
zigbee.configureReporting(0x0001, 0x20, 0x20, 30, 21600, 0x01) +
zigbee.enrollResponse() +
zigbee.readAttribute(0x0001, 0x20) +
cmds
}
private Map getButtonResult(buttonState, buttonNumber = 1) {
if (buttonState == 'release') {
log.debug "Button was value : $buttonState"
def timeDiff = now() - state.pressTime
log.info "timeDiff: $timeDiff"
def holdPreference = holdTime ?: 1
log.info "holdp1 : $holdPreference"
holdPreference = (holdPreference as int) * 1000
log.info "holdp2 : $holdPreference"
if (timeDiff > 10000) { //timeDiff>10sec check for refresh sending release value causing actions to be executed
return [:]
}
else {
if (timeDiff < holdPreference) {
buttonState = "pushed"
}
else {
buttonState = "held"
}
def descriptionText = "$device.displayName button $buttonNumber was $buttonState"
return createEvent(name: "button", value: buttonState, data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
}
}
else if (buttonState == 'press') {
log.debug "Button was value : $buttonState"
state.pressTime = now()
log.info "presstime: ${state.pressTime}"
return [:]
}
}
def installed() {
initialize()
def descMap = zigbee.parseDescriptionAsMap(description)
logIt descMap
state.lastCheckin = now()
logIt "lastCheckin = ${state.lastCheckin}"
handlePresenceEvent(true)
def results = []
if (description?.startsWith('catchall:'))
results = parseCatchAllMessage(descMap)
else if (description?.startsWith('read attr -'))
results = parseReportAttributeMessage(descMap)
else logIt(descMap, "trace")
return results;
}
def updated() {
initialize()
startTimer()
configure()
}
def initialize() {
if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) {
sendEvent(name: "numberOfButtons", value: 2)
}
else if ((device.getDataValue("manufacturer") == "CentraLite") &&
((device.getDataValue("model") == "3455-L") || (device.getDataValue("model") == "3460-L"))) {
sendEvent(name: "numberOfButtons", value: 1)
}
else if ((device.getDataValue("manufacturer") == "CentraLite") && (device.getDataValue("model") == "3450-L")) {
sendEvent(name: "numberOfButtons", value: 4)
}
else {
//default. can be changed
sendEvent(name: "numberOfButtons", value: 4)
}
def configure(){
logIt "Configuring Smart Fob..."
[
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 200"
] +
zigbee.configureReporting(0x0001,0x0020,0x20,20,20,0x01)
}
def parseCatchAllMessage(descMap) {
if (descMap?.clusterId == "0006" && descMap?.command == "01") //button pressed
handleButtonPress(descMap.sourceEndpoint as int)
else if (descMap?.clusterId == "0006" && descMap?.command == "00") //button released
handleButtonRelease(descMap.sourceEndpoint as int)
else logIt("Parse: Unhandled message: ${descMap}","trace")
}
def parseReportAttributeMessage(descMap) {
if (descMap?.cluster == "0001" && descMap?.attrId == "0020") createBatteryEvent(getBatteryLevel(descMap.value))
else logIt descMap
}
private createBatteryEvent(percent) {
logIt "Battery level at " + percent
return createEvent([name: "battery", value: percent])
}
//this method determines if a press should count as a push or a hold and returns the relevant event type
private handleButtonRelease(button) {
logIt "lastPress state variable: ${state.lastPress}"
def sequenceError = {logIt("Uh oh...missed a message? Dropping this event.", "error"); state.lastPress = null; return []}
if (!state.lastPress) return sequenceError()
else if (state.lastPress.button != button) return sequenceError()
def currentTime = now()
def startOfPress = state.lastPress?.time
def timeDif = currentTime - startOfPress
def holdTimeMillisec = (settings.holdTime?:3).toInteger() * 1000
state.lastPress = null //we're done with this. clear it to make error conditions easier to catch
if (timeDif < 0)
//likely a message sequence issue or dropped packet. Drop this press and wait for another.
return sequenceError()
else if (timeDif < holdTimeMillisec)
return createButtonEvent(button,"pushed")
else
return createButtonEvent(button,"held")
}
private handleButtonPress(button) {
state.lastPress = [button: button, time: now()]
}
private createButtonEvent(button,action) {
logIt "Button ${button} ${action}"
return createEvent([
name: "button",
value: action,
data:[buttonNumber: button],
descriptionText: "${device.displayName} button ${button} was ${action}",
isStateChange: true,
displayed: true])
}
private getBatteryLevel(rawValue) {
def intValue = Integer.parseInt(rawValue,16)
def min = 2.1
def max = 3.0
def vBatt = intValue / 10
return ((vBatt - min) / (max - min) * 100) as int
}
private handlePresenceEvent(present) {
def wasPresent = device.currentState("presence")?.value == "present"
if (!wasPresent && present) {
logIt "Sensor is present"
startTimer()
} else if (!present) {
logIt "Sensor is not present"
stopTimer()
}
def linkText = getLinkText(device)
def eventMap = [
name: "presence",
value: present ? "present" : "not present",
linkText: linkText,
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
]
logIt "Creating presence event: ${eventMap}"
sendEvent(eventMap)
}
private startTimer() {
logIt "Scheduling periodic timer"
schedule("0 * * * * ?", checkPresenceCallback)
}
private stopTimer() {
logIt "Stopping periodic timer"
unschedule()
}
def checkPresenceCallback() {
def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
logIt "Sensor checked in ${timeSinceLastCheckin} seconds ago"
if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false)
}
}
// ****** Utility functions ******
private logIt(str, logLevel = 'debug') {if (settings.logging) log."$logLevel"(str) }

View File

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

View File

@@ -1,41 +0,0 @@
# GE Plug-In/In-Wall Smart Dimmer (ZigBee)
Cloud Execution
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 Power Dimmer with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* __12min__ checkInterval
## 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 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
refresh()
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh()
}

View File

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

View File

@@ -1,39 +0,0 @@
# Zigbee Dimmer
Cloud Execution
Works with:
* [OSRAM Lightify LED On/Off/Dim](https://shop.smartthings.com/#!/products/osram-led-smart-bulb-on-off-dim)
* [WeMo LED Bulb](https://support.smartthings.com/hc/en-us/articles/204259040-Belkin-WeMo-LED-Bulb-F7C033-)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## 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
* **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
ZigBee Dimmer with reporting interval of 5 mins.
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
* __12min__ checkInterval
## 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.
Other troubleshooting tips are listed as follows:
* [OSRAM Lightify LED On/Off/Dim Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim)
* [WeMo LED Bulb Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204259040-Belkin-WeMo-LED-Bulb-F7C033-)

View File

@@ -19,7 +19,6 @@ metadata {
capability "Refresh"
capability "Switch"
capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
@@ -54,27 +53,11 @@ def parse(String description) {
def event = zigbee.getEvent(description)
if (event) {
if (event.name=="level" && event.value==0) {}
else {
sendEvent(event)
}
sendEvent(event)
}
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 "${cluster}"
}
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
}
}
@@ -89,23 +72,12 @@ def on() {
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.onOffConfig(0, 300) + zigbee.levelConfig()
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
}

View File

@@ -0,0 +1,243 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
//@Deprecated - Moved to zll-rgbw-bulb
/* Philips Hue (via Zigbee)
Capabilities:
Actuator
Color Control
Configuration
Polling
Refresh
Sensor
Switch
Switch Level
Custom Commands:
setAdjustedColor
*/
metadata {
definition (name: "Zigbee Hue Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Color Control"
capability "Switch"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Sensor"
command "setAdjustedColor"
//fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000", outClusters: "0019"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
state "color", action:"setAdjustedColor"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) {
state "saturation", action:"color control.setSaturation"
}
valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
state "saturation", label: 'Sat ${currentValue} '
}
controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) {
state "hue", action:"color control.setHue"
}
main(["switch"])
details(["switch", "levelSliderControl", "rgbSelector", "refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
//log.trace description
if (description?.startsWith("catchall:")) {
def msg = zigbee.parse(description)
//log.trace msg
//log.trace "data: $msg.data"
}
else {
def name = description?.startsWith("on/off: ") ? "switch" : null
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
def result = createEvent(name: name, value: value)
log.debug "Parse returned ${result?.descriptionText}"
return result
}
}
def on() {
// just assume it works for now
log.debug "on()"
sendEvent(name: "switch", value: "on")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
}
def off() {
// just assume it works for now
log.debug "off()"
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
def setHue(value) {
def max = 0xfe
log.trace "setHue($value)"
sendEvent(name: "hue", value: value)
def scaledValue = Math.round(value * max / 100.0)
def cmd = "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${hex(scaledValue)} 00 0000}"
//log.info cmd
cmd
}
def setAdjustedColor(value) {
log.debug "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
adjusted.level = null // needed because color picker always sends 100
setColor(adjusted)
}
def setColor(value){
log.trace "setColor($value)"
def max = 0xfe
sendEvent(name: "hue", value: value.hue)
sendEvent(name: "saturation", value: value.saturation)
def scaledHueValue = Math.round(value.hue * max / 100.0)
def scaledSatValue = Math.round(value.saturation * max / 100.0)
def cmd = []
if (value.switch != "off" && device.latestValue("switch") == "off") {
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
cmd << "delay 150"
}
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${hex(scaledHueValue)} 00 0000}"
cmd << "delay 150"
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${hex(scaledSatValue)} 0000}"
if (value.level != null) {
cmd << "delay 150"
cmd.addAll(setLevel(value.level))
}
if (value.switch == "off") {
cmd << "delay 150"
cmd << off()
}
log.info cmd
cmd
}
def setSaturation(value) {
def max = 0xfe
log.trace "setSaturation($value)"
sendEvent(name: "saturation", value: value)
def scaledValue = Math.round(value * max / 100.0)
def cmd = "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${hex(scaledValue)} 0000}"
//log.info cmd
cmd
}
def refresh() {
"st rattr 0x${device.deviceNetworkId} 1 6 0"
}
def poll(){
log.debug "Poll is calling refresh"
refresh()
}
def setLevel(value) {
log.trace "setLevel($value)"
def cmds = []
if (value == 0) {
sendEvent(name: "switch", value: "off")
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
else if (device.latestValue("switch") == "off") {
sendEvent(name: "switch", value: "on")
}
sendEvent(name: "level", value: value)
def level = hexString(Math.round(value * 255/100))
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
//log.debug cmds
cmds
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}

View File

@@ -31,8 +31,7 @@
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD226/246 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
}
}
tiles(scale: 2) {
multiAttributeTile(name:"toggle", type:"generic", width:6, height:4){
@@ -90,7 +89,7 @@ def configure() {
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,
TYPE_U8, 600, 21600, 0x01)
log.info "configure() --- cmds: $cmds"
return refresh() + cmds // send refresh cmds as part of config
return cmds + refresh() // send refresh cmds as part of config
}
def refresh() {

View File

@@ -1,154 +0,0 @@
/**
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Author: SmartThings
* Date: 2016-01-19
*
* This DTH should serve as the generic DTH to handle RGB ZigBee HA devices (For color bulbs with no color temperature)
*/
metadata {
definition (name: "ZigBee RGB Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Color Control"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Switch"
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"
}
// UI tile definitions
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.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"color control.setColor"
}
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["switch", "refresh"])
}
}
//Globals
private getATTRIBUTE_HUE() { 0x0000 }
private getATTRIBUTE_SATURATION() { 0x0001 }
private getHUE_COMMAND() { 0x00 }
private getSATURATION_COMMAND() { 0x03 }
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) {
log.debug event
if (event.name=="level" && event.value==0) {}
else {
sendEvent(event)
}
}
else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
def cluster = zigbee.parse(description)
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
}
}
else 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.info "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbeeMap
}
}
}
def on() {
zigbee.on()
}
def off() {
zigbee.off()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.onOffRefresh()
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// 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])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}
def setLevel(value) {
zigbee.setLevel(value)
}
def setColor(value){
log.trace "setColor($value)"
zigbee.on() + setHue(value.hue) + "delay 500" + setSaturation(value.saturation)
}
def setHue(value) {
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
}
def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + "delay 1000" + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}

Some files were not shown because too many files have changed in this diff Show More