mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 21:03:00 +00:00
Compare commits
1 Commits
PROD_2017.
...
DEVC-484
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e4e06bbf3 |
@@ -1,10 +1,10 @@
|
||||
# SmartThings Public GitHub Repo
|
||||
# SmartThings Public Github Repo
|
||||
|
||||
An official list of SmartApps and Device Types from SmartThings.
|
||||
|
||||
Here are some links to help you get started coding right away:
|
||||
|
||||
* [GitHub-specific Documentation](http://docs.smartthings.com/en/latest/tools-and-ide/github-integration.html)
|
||||
* [Github-specific Documentation](http://docs.smartthings.com/en/latest/tools-and-ide/github-integration.html)
|
||||
* [Full Documentation](http://docs.smartthings.com)
|
||||
* [IDE & Simulator](http://ide.smartthings.com)
|
||||
* [Community Forums](http://community.smartthings.com)
|
||||
|
||||
@@ -15,7 +15,6 @@ metadata {
|
||||
definition (name: "Aeon Key Fob", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Button"
|
||||
capability "Holdable Button"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
@@ -119,16 +118,3 @@ def configure() {
|
||||
log.debug("Sending configuration: $cmd")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
||||
def installed() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
sendEvent(name: "numberOfButtons", value: 4)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ metadata {
|
||||
definition (name: "Aeon Minimote", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Button"
|
||||
capability "Holdable Button"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
|
||||
@@ -108,16 +107,3 @@ def configure() {
|
||||
log.debug("Sending configuration: $cmds")
|
||||
return cmds
|
||||
}
|
||||
|
||||
|
||||
def installed() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
sendEvent(name: "numberOfButtons", value: 4)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Tone"
|
||||
@@ -59,7 +60,7 @@ def updated() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
def cmds = zigbee.batteryConfig(20, 20, 0x01)
|
||||
def cmds = zigbee.configureReporting(0x0001, 0x0020, 0x20, 20, 20, 0x01)
|
||||
log.debug "configure -- cmds: ${cmds}"
|
||||
return cmds
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Music Player"
|
||||
capability "Health Check"
|
||||
capability "Polling"
|
||||
|
||||
/**
|
||||
* Define all commands, ie, if you have a custom action not
|
||||
@@ -236,33 +236,7 @@ def parse(String event) {
|
||||
* @return action(s) to take or null
|
||||
*/
|
||||
def installed() {
|
||||
// Notify health check about this device with timeout interval 12 minutes
|
||||
sendEvent(name: "checkInterval", value: 12 * 60, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
||||
startPoll()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by health check if no events been generated in the last 12 minutes
|
||||
* If device doesn't respond it will be marked offline (not available)
|
||||
*/
|
||||
def ping() {
|
||||
TRACE("ping")
|
||||
boseSendGetNowPlaying()
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a 2 minute poll of the device to refresh the
|
||||
* tiles so the user gets the correct information.
|
||||
*/
|
||||
def startPoll() {
|
||||
TRACE("startPoll")
|
||||
unschedule()
|
||||
// Schedule 2 minute polling of speaker status (song average length is 3-4 minutes)
|
||||
def sec = Math.round(Math.floor(Math.random() * 60))
|
||||
//def cron = "$sec 0/5 * * * ?" // every 5 min
|
||||
def cron = "$sec 0/2 * * * ?" // every 2 min
|
||||
log.debug "schedule('$cron', boseSendGetNowPlaying)"
|
||||
schedule(cron, boseSendGetNowPlaying)
|
||||
onAction("refresh")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,6 +316,14 @@ def onAction(String user, data=null) {
|
||||
return actions
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every so often (every 5 minutes actually) to refresh the
|
||||
* tiles so the user gets the correct information.
|
||||
*/
|
||||
def poll() {
|
||||
return boseRefreshNowPlaying()
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins this speaker into the everywhere zone
|
||||
*/
|
||||
@@ -855,10 +837,6 @@ def boseRefreshNowPlaying(delay=0) {
|
||||
return boseGET("/now_playing")
|
||||
}
|
||||
|
||||
def boseSendGetNowPlaying() {
|
||||
sendHubCommand(boseGET("/now_playing"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the list of presets
|
||||
*
|
||||
@@ -1036,8 +1014,4 @@ def boseGetDeviceID() {
|
||||
*/
|
||||
def getDeviceIP() {
|
||||
return parent.resolveDNI2Address(device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def TRACE(text) {
|
||||
log.trace "${text}"
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
}
|
||||
|
||||
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.refresh", 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 "description is $description"
|
||||
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
log.info event
|
||||
if (event.name == "power") {
|
||||
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||
event.value = (event.value as Integer) / 10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
sendEvent(event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
refresh()
|
||||
}
|
||||
@@ -81,47 +81,51 @@ metadata {
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parse description $description"
|
||||
List result = []
|
||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||
log.debug "Desc Map: $descMap"
|
||||
List attrData = [[cluster: descMap.cluster ,attrId: descMap.attrId, value: descMap.value]]
|
||||
descMap.additionalAttrs.each {
|
||||
attrData << [cluster: descMap.cluster, attrId: it.attrId, value: it.value]
|
||||
}
|
||||
attrData.each {
|
||||
def map = [:]
|
||||
if (it.cluster == "0201" && it.attrId == "0000") {
|
||||
def map = [:]
|
||||
if (description?.startsWith("read attr -")) {
|
||||
def descMap = parseDescriptionAsMap(description)
|
||||
log.debug "Desc Map: $descMap"
|
||||
if (descMap.cluster == "0201" && descMap.attrId == "0000") {
|
||||
log.debug "TEMP"
|
||||
map.name = "temperature"
|
||||
map.value = getTemperature(it.value)
|
||||
map.value = getTemperature(descMap.value)
|
||||
map.unit = temperatureScale
|
||||
} else if (it.cluster == "0201" && it.attrId == "0011") {
|
||||
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
|
||||
log.debug "COOLING SETPOINT"
|
||||
map.name = "coolingSetpoint"
|
||||
map.value = getTemperature(it.value)
|
||||
map.value = getTemperature(descMap.value)
|
||||
map.unit = temperatureScale
|
||||
} else if (it.cluster == "0201" && it.attrId == "0012") {
|
||||
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
|
||||
log.debug "HEATING SETPOINT"
|
||||
map.name = "heatingSetpoint"
|
||||
map.value = getTemperature(it.value)
|
||||
map.value = getTemperature(descMap.value)
|
||||
map.unit = temperatureScale
|
||||
} else if (it.cluster == "0201" && it.attrId == "001c") {
|
||||
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
|
||||
log.debug "MODE"
|
||||
map.name = "thermostatMode"
|
||||
map.value = getModeMap()[it.value]
|
||||
} else if (it.cluster == "0202" && it.attrId == "0000") {
|
||||
map.value = getModeMap()[descMap.value]
|
||||
} else if (descMap.cluster == "0202" && descMap.attrId == "0000") {
|
||||
log.debug "FAN MODE"
|
||||
map.name = "thermostatFanMode"
|
||||
map.value = getFanModeMap()[it.value]
|
||||
map.value = getFanModeMap()[descMap.value]
|
||||
}
|
||||
if (map) {
|
||||
result << createEvent(map)
|
||||
}
|
||||
log.debug "Parse returned $map"
|
||||
}
|
||||
|
||||
def result = null
|
||||
if (map) {
|
||||
result = createEvent(map)
|
||||
}
|
||||
log.debug "Parse returned $map"
|
||||
return result
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
|
||||
def getModeMap() { [
|
||||
"00":"off",
|
||||
"03":"cool",
|
||||
|
||||
@@ -16,7 +16,7 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Button"
|
||||
capability "Actuator"
|
||||
capability "Actuator"
|
||||
|
||||
//fingerprint deviceId: "0x1200", inClusters: "0x77 0x86 0x75 0x73 0x85 0x72 0xEF", outClusters: "0x26"
|
||||
}
|
||||
@@ -74,20 +74,20 @@ def off() {
|
||||
}
|
||||
|
||||
def levelup() {
|
||||
def curlevel = device.currentValue('level') as Integer
|
||||
def curlevel = device.currentValue('level') as Integer
|
||||
if (curlevel <= 90)
|
||||
setLevel(curlevel + 10);
|
||||
setLevel(curlevel + 10);
|
||||
}
|
||||
|
||||
def leveldown() {
|
||||
def curlevel = device.currentValue('level') as Integer
|
||||
def curlevel = device.currentValue('level') as Integer
|
||||
if (curlevel >= 10)
|
||||
setLevel(curlevel - 10)
|
||||
setLevel(curlevel - 10)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
log.trace "setLevel($value)"
|
||||
sendEvent(name: "level", value: value)
|
||||
sendEvent(name: "level", value: value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) {
|
||||
@@ -106,11 +106,11 @@ def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelS
|
||||
if (cmd.upDown == true) {
|
||||
Integer buttonid = 2
|
||||
leveldown()
|
||||
checkbuttonEvent(buttonid)
|
||||
checkbuttonEvent(buttonid)
|
||||
} else if (cmd.upDown == false) {
|
||||
Integer buttonid = 3
|
||||
levelup()
|
||||
checkbuttonEvent(buttonid)
|
||||
checkbuttonEvent(buttonid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,12 +140,12 @@ def buttonEvent(button) {
|
||||
def result = []
|
||||
if (button == 1) {
|
||||
def mystate = device.currentValue('switch');
|
||||
if (mystate == "on")
|
||||
if (mystate == "on")
|
||||
off()
|
||||
else
|
||||
on()
|
||||
on()
|
||||
}
|
||||
updateState("currentButton", "$button")
|
||||
updateState("currentButton", "$button")
|
||||
// update the device state, recording the button press
|
||||
result << createEvent(name: "button", value: "pushed", data: [buttonNumber: button], descriptionText: "$device.displayName button $button was pushed", isStateChange: true)
|
||||
result
|
||||
@@ -182,16 +182,3 @@ def updateState(String name, String value) {
|
||||
state[name] = value
|
||||
device.updateDataValue(name, value)
|
||||
}
|
||||
|
||||
|
||||
def installed() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
sendEvent(name: "numberOfButtons", value: 3)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Connected Cree LED Bulb
|
||||
|
||||
Cloud Execution
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -23,10 +23,8 @@ Works with:
|
||||
|
||||
## 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
|
||||
A Category C6 Connected Cree LED Bulb with maxReportTime of 5 mins.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
|
||||
}
|
||||
@@ -83,7 +82,7 @@ def on() {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,7 +105,7 @@ def healthPoll() {
|
||||
def configure() {
|
||||
unschedule()
|
||||
runEvery5Minutes("healthPoll")
|
||||
// Device-Watch allows 2 check-in misses from device + ping
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Z-wave Dimmer Switch
|
||||
|
||||
Local Execution on V2 Hubs
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -34,8 +34,6 @@ Not to mention after going OFFLINE when the device is plugged back in, it might
|
||||
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.
|
||||
|
||||
@@ -21,12 +21,10 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "GE In-Wall Smart Dimmer "
|
||||
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "GE In-Wall Smart Dimmer "
|
||||
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "GE Plug-In Smart Dimmer "
|
||||
fingerprint mfr:"0063", prod:"4944", model:"3034", deviceJoinName: "GE In-Wall Smart Fan Control"
|
||||
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"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -238,17 +236,17 @@ def refresh() {
|
||||
}
|
||||
|
||||
void indicatorWhenOn() {
|
||||
sendEvent(name: "indicatorStatus", value: "when on", displayed: false)
|
||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
void indicatorWhenOff() {
|
||||
sendEvent(name: "indicatorStatus", value: "when off", displayed: false)
|
||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
void indicatorNever() {
|
||||
sendEvent(name: "indicatorStatus", value: "never", displayed: false)
|
||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ 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)
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "cloud", hubHardwareId: device.hub.hardwareID], displayed: false)
|
||||
}
|
||||
|
||||
// Device Watch will ping the device to proactively determine if the device has gone offline
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Configuration"
|
||||
capability "Configuration"
|
||||
capability "Color Control"
|
||||
capability "Power Meter"
|
||||
|
||||
|
||||
command "getDeviceData"
|
||||
command "softwhite"
|
||||
command "daylight"
|
||||
@@ -54,12 +54,12 @@
|
||||
command "setAdjustedColor"
|
||||
command "setWhiteLevel"
|
||||
command "test"
|
||||
|
||||
|
||||
attribute "whiteLevel", "string"
|
||||
|
||||
|
||||
fingerprint deviceId: "0x1101", inClusters: "0x27,0x72,0x86,0x26,0x60,0x70,0x32,0x31,0x85,0x33"
|
||||
}
|
||||
|
||||
|
||||
simulator {
|
||||
status "on": "command: 2003, payload: FF"
|
||||
status "off": "command: 2003, payload: 00"
|
||||
@@ -84,7 +84,7 @@
|
||||
}
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
controlTile("whiteSliderControl", "device.whiteLevel", "slider", height: 1, width: 3, inactiveLabel: false) {
|
||||
state "whiteLevel", action:"setWhiteLevel", label:'White Level'
|
||||
}
|
||||
@@ -183,24 +183,24 @@
|
||||
valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") {
|
||||
state "hue", label: 'Hue ${currentValue} '
|
||||
}
|
||||
|
||||
|
||||
main(["switch"])
|
||||
details(["switch",
|
||||
"levelSliderControl",
|
||||
"rgbSelector",
|
||||
"whiteSliderControl",
|
||||
details(["switch",
|
||||
"levelSliderControl",
|
||||
"rgbSelector",
|
||||
"whiteSliderControl",
|
||||
/*"softwhite",
|
||||
"daylight",
|
||||
"warmwhite",
|
||||
"red",
|
||||
"green",
|
||||
"red",
|
||||
"green",
|
||||
"blue",
|
||||
"white",
|
||||
"cyan",
|
||||
"magenta",
|
||||
"orange",
|
||||
"purple",
|
||||
"yellow",
|
||||
"yellow",
|
||||
"fireplace",
|
||||
"storm",
|
||||
"deepfade",
|
||||
@@ -214,7 +214,7 @@
|
||||
|
||||
def setAdjustedColor(value) {
|
||||
log.debug "setAdjustedColor: ${value}"
|
||||
|
||||
|
||||
toggleTiles("off") //turn off the hard color tiles
|
||||
|
||||
def level = device.latestValue("level")
|
||||
@@ -223,19 +223,19 @@ def setAdjustedColor(value) {
|
||||
log.debug "level is: ${level}"
|
||||
value.level = level
|
||||
|
||||
def c = hexToRgb(value.hex)
|
||||
def c = hexToRgb(value.hex)
|
||||
value.rh = hex(c.r * (level/100))
|
||||
value.gh = hex(c.g * (level/100))
|
||||
value.bh = hex(c.b * (level/100))
|
||||
|
||||
setColor(value)
|
||||
|
||||
setColor(value)
|
||||
}
|
||||
|
||||
def setColor(value) {
|
||||
log.debug "setColor: ${value}"
|
||||
log.debug "hue is: ${value.hue}"
|
||||
log.debug "saturation is: ${value.saturation}"
|
||||
|
||||
|
||||
if (value.size() < 8)
|
||||
toggleTiles("off")
|
||||
|
||||
@@ -246,22 +246,22 @@ def setColor(value) {
|
||||
value.gh = hex(rgb.g)
|
||||
value.bh = hex(rgb.b)
|
||||
}
|
||||
|
||||
|
||||
if ((value.size() == 3) && (value.hue != null) && (value.saturation != null) && (value.level)) { //user passed in a level value too from outside (App)
|
||||
def rgb = hslToRGB(value.hue, value.saturation, 0.5)
|
||||
value.hex = rgbToHex(rgb)
|
||||
value.rh = hex(rgb.r * value.level/100)
|
||||
value.gh = hex(rgb.g * value.level/100)
|
||||
value.bh = hex(rgb.b * value.level/100)
|
||||
value.bh = hex(rgb.b * value.level/100)
|
||||
}
|
||||
|
||||
|
||||
if (( value.size() == 1) && (value.hex)) { //being called from outside of device (App) with only hex
|
||||
def rgbInt = hexToRgb(value.hex)
|
||||
value.rh = hex(rgbInt.r)
|
||||
value.gh = hex(rgbInt.g)
|
||||
value.bh = hex(rgbInt.b)
|
||||
}
|
||||
|
||||
|
||||
if (( value.size() == 2) && (value.hex) && (value.level)) { //being called from outside of device (App) with only hex and level
|
||||
|
||||
def rgbInt = hexToRgb(value.hex)
|
||||
@@ -269,7 +269,7 @@ def setColor(value) {
|
||||
value.gh = hex(rgbInt.g * value.level/100)
|
||||
value.bh = hex(rgbInt.b * value.level/100)
|
||||
}
|
||||
|
||||
|
||||
if (( value.size() == 1) && (value.colorName)) { //being called from outside of device (App) with only color name
|
||||
def colorData = getColorData(value.colorName)
|
||||
value.rh = colorData.rh
|
||||
@@ -277,7 +277,7 @@ def setColor(value) {
|
||||
value.bh = colorData.bh
|
||||
value.hex = "#${value.rh}${value.gh}${value.bh}"
|
||||
}
|
||||
|
||||
|
||||
if (( value.size() == 2) && (value.colorName) && (value.level)) { //being called from outside of device (App) with only color name and level
|
||||
def colorData = getColorData(value.colorName)
|
||||
value.rh = hex(colorData.r * value.level/100)
|
||||
@@ -285,7 +285,7 @@ def setColor(value) {
|
||||
value.bh = hex(colorData.b * value.level/100)
|
||||
value.hex = "#${hex(colorData.r)}${hex(colorData.g)}${hex(colorData.b)}"
|
||||
}
|
||||
|
||||
|
||||
if (( value.size() == 3) && (value.red != null) && (value.green != null) && (value.blue != null)) { //being called from outside of device (App) with only color values (0-255)
|
||||
value.rh = hex(value.red)
|
||||
value.gh = hex(value.green)
|
||||
@@ -299,7 +299,7 @@ def setColor(value) {
|
||||
value.bh = hex(value.blue * value.level/100)
|
||||
value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}"
|
||||
}
|
||||
|
||||
|
||||
sendEvent(name: "hue", value: value.hue, displayed: false)
|
||||
sendEvent(name: "saturation", value: value.saturation, displayed: false)
|
||||
sendEvent(name: "color", value: value.hex, displayed: false)
|
||||
@@ -309,26 +309,26 @@ def setColor(value) {
|
||||
if (value.switch) {
|
||||
sendEvent(name: "switch", value: value.switch)
|
||||
}
|
||||
|
||||
|
||||
sendRGB(value.rh, value.gh, value.bh)
|
||||
}
|
||||
|
||||
def setLevel(level) {
|
||||
log.debug "setLevel($level)"
|
||||
|
||||
|
||||
if (level == 0) { off() }
|
||||
else if (device.latestValue("switch") == "off") { on() }
|
||||
|
||||
|
||||
def colorHex = device.latestValue("color")
|
||||
if (colorHex == null)
|
||||
colorHex = "#FFFFFF"
|
||||
|
||||
|
||||
def c = hexToRgb(colorHex)
|
||||
|
||||
|
||||
def r = hex(c.r * (level/100))
|
||||
def g = hex(c.g * (level/100))
|
||||
def b = hex(c.b * (level/100))
|
||||
|
||||
|
||||
sendEvent(name: "level", value: level)
|
||||
sendEvent(name: "setLevel", value: level, displayed: false)
|
||||
sendRGB(r, g, b)
|
||||
@@ -337,14 +337,14 @@ def setLevel(level) {
|
||||
|
||||
def setWhiteLevel(value) {
|
||||
log.debug "setWhiteLevel: ${value}"
|
||||
def level = Math.min(value as Integer, 99)
|
||||
def level = Math.min(value as Integer, 99)
|
||||
level = 255 * level/99 as Integer
|
||||
def channel = 0
|
||||
|
||||
if (device.latestValue("switch") == "off") { on() }
|
||||
|
||||
|
||||
sendEvent(name: "whiteLevel", value: value)
|
||||
sendWhite(channel, value)
|
||||
sendWhite(channel, value)
|
||||
}
|
||||
|
||||
def sendWhite(channel, value) {
|
||||
@@ -367,20 +367,20 @@ def sendRGBW(redHex, greenHex, blueHex, whiteHex) {
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Device For SmartThings Use"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def cmds = []
|
||||
|
||||
|
||||
// send associate to group 3 to get sensor data reported only to hub
|
||||
cmds << zwave.associationV2.associationSet(groupingIdentifier:5, nodeId:[zwaveHubNodeId]).format()
|
||||
|
||||
|
||||
|
||||
|
||||
//cmds << sendEvent(name: "level", value: 50)
|
||||
//cmds << on()
|
||||
//cmds << doColorButton("Green")
|
||||
delayBetween(cmds, 500)
|
||||
|
||||
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
@@ -411,11 +411,11 @@ def parse(String description) {
|
||||
|
||||
def getDeviceData() {
|
||||
def cmd = []
|
||||
|
||||
cmd << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
|
||||
cmd << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
cmd << response(zwave.versionV1.versionGet())
|
||||
cmd << response(zwave.firmwareUpdateMdV1.firmwareMdGet())
|
||||
|
||||
|
||||
delayBetween(cmd, 500)
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ def createEvent(physicalgraph.zwave.commands.manufacturerspecificv2.Manufacturer
|
||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map item1) {
|
||||
def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map item1) {
|
||||
updateDataValue("applicationVersion", "${cmd.applicationVersion}")
|
||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
||||
@@ -435,13 +435,13 @@ def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map it
|
||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.firmwareupdatemdv1.FirmwareMdReport cmd, Map item1) {
|
||||
def createEvent(physicalgraph.zwave.commands.firmwareupdatemdv1.FirmwareMdReport cmd, Map item1) {
|
||||
log.debug "checksum: ${cmd.checksum}"
|
||||
log.debug "firmwareId: ${cmd.firmwareId}"
|
||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.colorcontrolv1.CapabilityReport cmd, Map item1) {
|
||||
def zwaveEvent(physicalgraph.zwave.commands.colorcontrolv1.CapabilityReport cmd, Map item1) {
|
||||
|
||||
log.debug "In CapabilityReport"
|
||||
}
|
||||
@@ -546,7 +546,7 @@ def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport
|
||||
def value = "when off"
|
||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
||||
[name: "indicatorStatus", value: value, displayed: false]
|
||||
[name: "indicatorStatus", value: value, display: false]
|
||||
}
|
||||
*/
|
||||
def createEvent(physicalgraph.zwave.Command cmd, Map map) {
|
||||
@@ -557,7 +557,7 @@ def createEvent(physicalgraph.zwave.Command cmd, Map map) {
|
||||
def on() {
|
||||
log.debug "on()"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||
delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
||||
}
|
||||
|
||||
@@ -593,7 +593,7 @@ def refresh() {
|
||||
* @return none
|
||||
*/
|
||||
def updateZwaveParam(params) {
|
||||
if ( params ) {
|
||||
if ( params ) {
|
||||
def pNumber = params.paramNumber
|
||||
def pSize = params.size
|
||||
def pValue = [params.value]
|
||||
@@ -601,9 +601,9 @@ def updateZwaveParam(params) {
|
||||
|
||||
def cmds = []
|
||||
cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format()
|
||||
|
||||
|
||||
cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format()
|
||||
delayBetween(cmds, 1500)
|
||||
delayBetween(cmds, 1500)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,22 +612,22 @@ def test() {
|
||||
//value = [hue: 0, saturation: 100, level: 5]
|
||||
//value = [red: 255, green: 0, blue: 255, level: 60]
|
||||
//setColor(value)
|
||||
|
||||
|
||||
def cmd = []
|
||||
|
||||
|
||||
if ( !state.cnt ) {
|
||||
state.cnt = 6
|
||||
} else {
|
||||
state.cnt = state.cnt + 1
|
||||
}
|
||||
|
||||
|
||||
if ( state.cnt > 10 )
|
||||
state.cnt = 6
|
||||
|
||||
|
||||
// run programmed light show
|
||||
cmd << zwave.configurationV1.configurationSet(configurationValue: [state.cnt], parameterNumber: 72, size: 1).format()
|
||||
cmd << zwave.configurationV1.configurationGet(parameterNumber: 72).format()
|
||||
|
||||
cmd << zwave.configurationV1.configurationGet(parameterNumber: 72).format()
|
||||
|
||||
delayBetween(cmd, 500)
|
||||
|
||||
}
|
||||
@@ -638,23 +638,23 @@ def colorNameToRgb(color) {
|
||||
[name:"Soft White", r: 255, g: 241, b: 224 ],
|
||||
[name:"Daylight", r: 255, g: 255, b: 251 ],
|
||||
[name:"Warm White", r: 255, g: 244, b: 229 ],
|
||||
|
||||
|
||||
[name:"Red", r: 255, g: 0, b: 0 ],
|
||||
[name:"Green", r: 0, g: 255, b: 0 ],
|
||||
[name:"Blue", r: 0, g: 0, b: 255 ],
|
||||
|
||||
|
||||
[name:"Cyan", r: 0, g: 255, b: 255 ],
|
||||
[name:"Magenta", r: 255, g: 0, b: 33 ],
|
||||
[name:"Magenta", r: 255, g: 0, b: 33 ],
|
||||
[name:"Orange", r: 255, g: 102, b: 0 ],
|
||||
|
||||
|
||||
[name:"Purple", r: 170, g: 0, b: 255 ],
|
||||
[name:"Yellow", r: 255, g: 255, b: 0 ],
|
||||
[name:"White", r: 255, g: 255, b: 255 ]
|
||||
]
|
||||
|
||||
def colorData = [:]
|
||||
|
||||
def colorData = [:]
|
||||
colorData = colors.find { it.name == color }
|
||||
|
||||
|
||||
colorData
|
||||
}
|
||||
|
||||
@@ -670,7 +670,7 @@ def hexToRgb(colorHex) {
|
||||
def rrInt = Integer.parseInt(colorHex.substring(1,3),16)
|
||||
def ggInt = Integer.parseInt(colorHex.substring(3,5),16)
|
||||
def bbInt = Integer.parseInt(colorHex.substring(5,7),16)
|
||||
|
||||
|
||||
def colorData = [:]
|
||||
colorData = [r: rrInt, g: ggInt, b: bbInt]
|
||||
colorData
|
||||
@@ -681,7 +681,7 @@ def rgbToHex(rgb) {
|
||||
def g = hex(rgb.g)
|
||||
def b = hex(rgb.b)
|
||||
def hexColor = "#${r}${g}${b}"
|
||||
|
||||
|
||||
hexColor
|
||||
}
|
||||
|
||||
@@ -689,11 +689,11 @@ def hslToRGB(float var_h, float var_s, float var_l) {
|
||||
float h = var_h / 100
|
||||
float s = var_s / 100
|
||||
float l = var_l
|
||||
|
||||
|
||||
def r = 0
|
||||
def g = 0
|
||||
def b = 0
|
||||
|
||||
|
||||
if (s == 0) {
|
||||
r = l * 255
|
||||
g = l * 255
|
||||
@@ -705,26 +705,26 @@ def hslToRGB(float var_h, float var_s, float var_l) {
|
||||
} else {
|
||||
var_2 = (l + s) - (s * l)
|
||||
}
|
||||
|
||||
|
||||
float var_1 = 2 * l - var_2
|
||||
|
||||
|
||||
r = 255 * hueToRgb(var_1, var_2, h + (1 / 3))
|
||||
g = 255 * hueToRgb(var_1, var_2, h)
|
||||
b = 255 * hueToRgb(var_1, var_2, h - (1 / 3))
|
||||
b = 255 * hueToRgb(var_1, var_2, h - (1 / 3))
|
||||
}
|
||||
|
||||
|
||||
def rgb = [:]
|
||||
rgb = [r: r, g: g, b: b]
|
||||
|
||||
rgb
|
||||
rgb
|
||||
}
|
||||
|
||||
def hueToRgb(v1, v2, vh) {
|
||||
if (vh < 0) { vh += 1 }
|
||||
if (vh < 0) { vh += 1 }
|
||||
if (vh > 1) { vh -= 1 }
|
||||
if ((6 * vh) < 1) { return (v1 + (v2 - v1) * 6 * vh) }
|
||||
if ((2 * vh) < 1) { return (v2) }
|
||||
if ((3 * vh) < 2) { return (v1 + (v2 - $v1) * ((2 / 3 - vh) * 6)) }
|
||||
if ((3 * vh) < 2) { return (v1 + (v2 - $v1) * ((2 / 3 - vh) * 6)) }
|
||||
return (v1)
|
||||
}
|
||||
|
||||
@@ -735,49 +735,49 @@ def rgbToHSL(rgb) {
|
||||
def h = 0
|
||||
def s = 0
|
||||
def l = 0
|
||||
|
||||
|
||||
def var_min = [r,g,b].min()
|
||||
def var_max = [r,g,b].max()
|
||||
def del_max = var_max - var_min
|
||||
|
||||
|
||||
l = (var_max + var_min) / 2
|
||||
|
||||
|
||||
if (del_max == 0) {
|
||||
h = 0
|
||||
s = 0
|
||||
} else {
|
||||
if (l < 0.5) { s = del_max / (var_max + var_min) }
|
||||
if (l < 0.5) { s = del_max / (var_max + var_min) }
|
||||
else { s = del_max / (2 - var_max - var_min) }
|
||||
|
||||
def del_r = (((var_max - r) / 6) + (del_max / 2)) / del_max
|
||||
def del_g = (((var_max - g) / 6) + (del_max / 2)) / del_max
|
||||
def del_b = (((var_max - b) / 6) + (del_max / 2)) / del_max
|
||||
|
||||
if (r == var_max) { h = del_b - del_g }
|
||||
else if (g == var_max) { h = (1 / 3) + del_r - del_b }
|
||||
if (r == var_max) { h = del_b - del_g }
|
||||
else if (g == var_max) { h = (1 / 3) + del_r - del_b }
|
||||
else if (b == var_max) { h = (2 / 3) + del_g - del_r }
|
||||
|
||||
|
||||
if (h < 0) { h += 1 }
|
||||
if (h > 1) { h -= 1 }
|
||||
}
|
||||
def hsl = [:]
|
||||
def hsl = [:]
|
||||
hsl = [h: h * 100, s: s * 100, l: l]
|
||||
|
||||
|
||||
hsl
|
||||
}
|
||||
|
||||
def getColorData(colorName) {
|
||||
log.debug "getColorData: ${colorName}"
|
||||
|
||||
|
||||
def colorRGB = colorNameToRgb(colorName)
|
||||
def colorHex = rgbToHex(colorRGB)
|
||||
def colorHSL = rgbToHSL(colorRGB)
|
||||
|
||||
|
||||
def colorData = [:]
|
||||
colorData = [h: colorHSL.h,
|
||||
s: colorHSL.s,
|
||||
l: device.latestValue("level"),
|
||||
r: colorRGB.r,
|
||||
colorData = [h: colorHSL.h,
|
||||
s: colorHSL.s,
|
||||
l: device.latestValue("level"),
|
||||
r: colorRGB.r,
|
||||
g: colorRGB.g,
|
||||
b: colorRGB.b,
|
||||
rh: hex(colorRGB.r),
|
||||
@@ -785,8 +785,8 @@ def getColorData(colorName) {
|
||||
bh: hex(colorRGB.b),
|
||||
hex: colorHex,
|
||||
alpha: 1]
|
||||
|
||||
colorData
|
||||
|
||||
colorData
|
||||
}
|
||||
|
||||
def doColorButton(colorName) {
|
||||
@@ -798,7 +798,7 @@ def doColorButton(colorName) {
|
||||
def maxLevel = hex(99)
|
||||
|
||||
toggleTiles(colorName.toLowerCase().replaceAll("\\s",""))
|
||||
|
||||
|
||||
if ( colorName == "Fire Place" ) { updateZwaveParam([paramNumber:72, value:6, size:1]) }
|
||||
else if ( colorName == "Storm" ) { updateZwaveParam([paramNumber:72, value:7, size:1]) }
|
||||
else if ( colorName == "Deep Fade" ) { updateZwaveParam([paramNumber:72, value:8, size:1]) }
|
||||
@@ -808,8 +808,8 @@ def doColorButton(colorName) {
|
||||
else if ( colorName == "Daylight" ) { String.format("33050400${maxLevel}02${maxLevel}03${maxLevel}04${maxLevel}%02X", 100) }
|
||||
else {
|
||||
def c = getColorData(colorName)
|
||||
def newValue = ["hue": c.h, "saturation": c.s, "level": level, "red": c.r, "green": c.g, "blue": c.b, "hex": c.hex, "alpha": c.alpha]
|
||||
setColor(newValue)
|
||||
def newValue = ["hue": c.h, "saturation": c.s, "level": level, "red": c.r, "green": c.g, "blue": c.b, "hex": c.hex, "alpha": c.alpha]
|
||||
setColor(newValue)
|
||||
def r = hex(c.r * (level/100))
|
||||
def g = hex(c.g * (level/100))
|
||||
def b = hex(c.b * (level/100))
|
||||
@@ -823,19 +823,19 @@ def toggleTiles(color) {
|
||||
if ( !state.colorTiles ) {
|
||||
state.colorTiles = ["softwhite","daylight","warmwhite","red","green","blue","cyan","magenta","orange","purple","yellow","white","fireplace","storm","deepfade","litefade","police"]
|
||||
}
|
||||
|
||||
|
||||
def cmds = []
|
||||
|
||||
|
||||
state.colorTiles.each({
|
||||
if ( it == color ) {
|
||||
log.debug "Turning ${it} on"
|
||||
cmds << sendEvent(name: it, value: "on${it}", displayed: True, descriptionText: "${device.displayName} ${color} is 'ON'", isStateChange: true)
|
||||
cmds << sendEvent(name: it, value: "on${it}", display: True, descriptionText: "${device.displayName} ${color} is 'ON'", isStateChange: true)
|
||||
} else {
|
||||
//log.debug "Turning ${it} off"
|
||||
cmds << sendEvent(name: it, value: "off${it}", displayed: false)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
delayBetween(cmds, 2500)
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ def parse(String description) {
|
||||
|
||||
def poll() {
|
||||
def refreshCmds = [
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 2000"
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
||||
]
|
||||
|
||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
@@ -197,7 +197,7 @@ def off() {
|
||||
|
||||
def refresh() {
|
||||
def refreshCmds = [
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 2000"
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
||||
]
|
||||
|
||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig()
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
}
|
||||
|
||||
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.refresh", 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 "description is $description"
|
||||
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
log.info event
|
||||
if (event.name == "power") {
|
||||
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||
event.value = (event.value as Integer) / 10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
sendEvent(event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
refresh()
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
}
|
||||
|
||||
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 ("power", key: "SECONDARY_CONTROL") {
|
||||
attributeState "power", label:'${currentValue} W'
|
||||
}
|
||||
}
|
||||
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"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
if (event.name == "power") {
|
||||
def powerValue
|
||||
powerValue = (event.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
}
|
||||
else {
|
||||
sendEvent(event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
refresh()
|
||||
}
|
||||
@@ -17,7 +17,6 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
command "setAdjustedColor"
|
||||
command "reset"
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Bridge"
|
||||
capability "Health Check"
|
||||
|
||||
attribute "networkAddress", "string"
|
||||
|
||||
@@ -18,7 +18,6 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
command "setAdjustedColor"
|
||||
command "reset"
|
||||
|
||||
@@ -14,8 +14,7 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
capability "Health Check"
|
||||
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ metadata {
|
||||
}
|
||||
|
||||
def generatePresenceEvent(boolean present) {
|
||||
log.info "Life360 generatePresenceEvent($present)"
|
||||
log.debug "Here in generatePresenceEvent!"
|
||||
def value = formatValue(present)
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = formatDescriptionText(linkText, present)
|
||||
|
||||
@@ -14,7 +14,6 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -13,7 +13,6 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -36,7 +36,6 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Contact Sensor"
|
||||
capability "Light"
|
||||
|
||||
attribute "powered", "string"
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@ def parse(String description) {
|
||||
}
|
||||
|
||||
def push() {
|
||||
sendEvent(name: "switch", value: "on", isStateChange: true, displayed: false)
|
||||
sendEvent(name: "switch", value: "off", isStateChange: true, displayed: false)
|
||||
sendEvent(name: "switch", value: "on", isStateChange: true, display: false)
|
||||
sendEvent(name: "switch", value: "off", isStateChange: true, display: false)
|
||||
sendEvent(name: "momentary", value: "pushed", isStateChange: true)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Nyce Door/Window Sensor (Open/Close Sensor)
|
||||
|
||||
Cloud Execution
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -23,11 +23,7 @@ Works with:
|
||||
|
||||
## 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
|
||||
|
||||
A Category C2 Nyce Door/Window sensor that has 12min check-in interval
|
||||
|
||||
## Battery Specification
|
||||
|
||||
|
||||
@@ -40,11 +40,14 @@ metadata {
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
def resMap
|
||||
if (description.startsWith("zone")) {
|
||||
resMap = createEvent(name: "contact", value: zigbee.parseZoneStatus(description).isAlarm1Set() ? "open" : "closed")
|
||||
def name = null
|
||||
def value = description
|
||||
if (zigbee.isZoneType19(description)) {
|
||||
name = "contact"
|
||||
value = zigbee.translateStatusZoneType19(description) ? "open" : "closed"
|
||||
}
|
||||
|
||||
log.debug "Parse returned $resMap"
|
||||
return resMap
|
||||
def result = createEvent(name: name, value: value)
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
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"
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorName", label: '${currentValue}'
|
||||
}
|
||||
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", "colorTempSliderControl", "colorName", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
//Globals
|
||||
private getATTRIBUTE_HUE() { 0x0000 }
|
||||
private getATTRIBUTE_SATURATION() { 0x0001 }
|
||||
private getHUE_COMMAND() { 0x00 }
|
||||
private getSATURATION_COMMAND() { 0x03 }
|
||||
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
|
||||
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
|
||||
|
||||
// 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 {
|
||||
if (event.name=="colorTemperature") {
|
||||
setGenericName(event.value)
|
||||
}
|
||||
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_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def 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
|
||||
refresh()
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
setGenericName(value)
|
||||
zigbee.setColorTemperature(value)
|
||||
}
|
||||
|
||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
||||
def setGenericName(value){
|
||||
if (value != null) {
|
||||
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 >= 5000) {
|
||||
genericName = "Daylight"
|
||||
}
|
||||
sendEvent(name: "colorName", value: genericName)
|
||||
}
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
log.trace "setColor($value)"
|
||||
zigbee.on() + setHue(value.hue) + "delay 300" + 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)
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
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"
|
||||
}
|
||||
|
||||
// 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.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorName", label: '${currentValue}'
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "colorTempSliderControl", "colorName", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
if (event.name=="level" && event.value==0) {}
|
||||
else {
|
||||
if (event.name=="colorTemperature") {
|
||||
setGenericName(event.value)
|
||||
}
|
||||
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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.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.colorTemperatureRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
|
||||
}
|
||||
|
||||
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
|
||||
refresh()
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
setGenericName(value)
|
||||
zigbee.setColorTemperature(value)
|
||||
}
|
||||
|
||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
||||
def setGenericName(value){
|
||||
if (value != null) {
|
||||
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 >= 5000) {
|
||||
genericName = "Daylight"
|
||||
}
|
||||
sendEvent(name: "colorName", value: genericName)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Actuator"
|
||||
capability "Sensor"
|
||||
capability "Outlet"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-ZHAC"
|
||||
|
||||
@@ -70,36 +69,286 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def event = zigbee.getEvent(description)
|
||||
if (!event) {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
} else if (event.name == "power") {
|
||||
/*
|
||||
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. Simplifying to 10 power level is an integer.
|
||||
*/
|
||||
event.value = event.value / 10
|
||||
}
|
||||
def event = [:]
|
||||
def finalResult = isKnownDescription(description)
|
||||
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)
|
||||
|
||||
return event ? createEvent(event) : event
|
||||
/*
|
||||
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 {
|
||||
event = createEvent(name: finalResult.type, value: finalResult.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug parseDescriptionAsMap(description)
|
||||
}
|
||||
return event
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
// Commands to device
|
||||
def zigbeeCommand(cluster, attribute){
|
||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} ${cluster} ${attribute} {}"
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
zigbeeCommand("6", "0")
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
zigbeeCommand("6", "1")
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
value = value as Integer
|
||||
if (value == 0) {
|
||||
off()
|
||||
}
|
||||
else {
|
||||
if (device.latestValue("switch") == "off") {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
sendEvent(name: "level", value: value)
|
||||
setLevelWithRate(value, "0000") //value is between 0 to 100
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.electricMeasurementPowerRefresh()
|
||||
[
|
||||
"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} 0x0B04 0x050B", "delay 500"
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
def configure() {
|
||||
refresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.electricMeasurementPowerConfig()
|
||||
refresh() + onOffConfig() + levelConfig() + powerConfig()
|
||||
}
|
||||
|
||||
|
||||
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 == "0B04" || descMap.clusterId == "0B04"){
|
||||
isDescriptionPower(descMap)
|
||||
}
|
||||
}
|
||||
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 == "0B04") {
|
||||
if (descMap.attrId == "050b") {
|
||||
if(descMap.value!="ffff")
|
||||
powerValue = convertHexToInt(descMap.value)
|
||||
}
|
||||
}
|
||||
else if (descMap.clusterId == "0B04") {
|
||||
if(descMap.command=="07"){
|
||||
return [type: "update", value : "power (0B04) 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 5 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() {
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ metadata {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Sensor"
|
||||
capability "Outlet"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0006, 0004, 0003, 0000, 0005", outClusters: "0019", manufacturer: "Compacta International, Ltd", model: "ZBMPlug15", deviceJoinName: "SmartPower Outlet V1"
|
||||
}
|
||||
@@ -52,7 +51,7 @@ def on() {
|
||||
'zcl on-off on',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 2000'
|
||||
'delay 500'
|
||||
|
||||
]
|
||||
|
||||
@@ -63,6 +62,6 @@ def off() {
|
||||
'zcl on-off off',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 2000'
|
||||
'delay 500'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SmartPower Outlet
|
||||
|
||||
Local Execution on V2 Hubs
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -23,11 +23,10 @@ Works with:
|
||||
|
||||
## 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
|
||||
A Category C1 smart power outlet with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition(name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Power Meter"
|
||||
@@ -23,12 +24,10 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
capability "Outlet"
|
||||
|
||||
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"
|
||||
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"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019"
|
||||
}
|
||||
|
||||
@@ -46,32 +45,32 @@ metadata {
|
||||
preferences {
|
||||
section {
|
||||
image(name: 'educationalcontent', multiple: true, images: [
|
||||
"http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS1.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS2.jpg"
|
||||
])
|
||||
"http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS1.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS2.jpg"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name: "switch", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
|
||||
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
||||
attributeState "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
||||
attributeState "turningOn", label: 'Turning On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
||||
attributeState "turningOff", label: 'Turning Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
||||
}
|
||||
tileAttribute("power", key: "SECONDARY_CONTROL") {
|
||||
attributeState "power", label: '${currentValue} W'
|
||||
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"
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main "switch"
|
||||
details(["switch", "refresh"])
|
||||
details(["switch","refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,33 +78,52 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def event = zigbee.getEvent(description)
|
||||
def finalResult = zigbee.getKnownDescription(description)
|
||||
def event = [:]
|
||||
|
||||
if (event) {
|
||||
if (event.name == "power") {
|
||||
def value = (event.value as Integer) / 10
|
||||
event = createEvent(name: event.name, value: value, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true)
|
||||
} else if (event.name == "switch") {
|
||||
def descriptionText = event.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
|
||||
event = createEvent(name: event.name, value: event.value, descriptionText: descriptionText, translatable: true)
|
||||
//TODO: Remove this after getKnownDescription can parse it automatically
|
||||
if (!finalResult && description!="updated")
|
||||
finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description))
|
||||
|
||||
if (finalResult) {
|
||||
log.info "final result = $finalResult"
|
||||
if (finalResult.type == "update") {
|
||||
log.info "$device updates: ${finalResult.value}"
|
||||
event = null
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
/*
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
else {
|
||||
def cluster = zigbee.parse(description)
|
||||
|
||||
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
|
||||
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 {
|
||||
}
|
||||
else {
|
||||
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||
event = null
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug "${cluster}"
|
||||
}
|
||||
}
|
||||
return event ? createEvent(event) : event
|
||||
return event
|
||||
}
|
||||
|
||||
def off() {
|
||||
@@ -127,11 +145,47 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||
// 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: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
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
|
||||
refresh() + zigbee.onOffConfig(0, 300) + zigbee.electricMeasurementPowerConfig()
|
||||
refresh() + zigbee.onOffConfig(0, 300) + powerConfig()
|
||||
}
|
||||
|
||||
//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 01
|
||||
def powerConfig() {
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
}
|
||||
|
||||
private getEndpointId() {
|
||||
new BigInteger(device.endpointId, 16).toString()
|
||||
}
|
||||
|
||||
//TODO: Remove this after getKnownDescription can parse it automatically
|
||||
def getPowerDescription(descMap) {
|
||||
def powerValue = "undefined"
|
||||
if (descMap.cluster == "0B04") {
|
||||
if (descMap.attrId == "050b") {
|
||||
if(descMap.value!="ffff")
|
||||
powerValue = zigbee.convertHexToInt(descMap.value)
|
||||
}
|
||||
}
|
||||
else if (descMap.clusterId == "0B04") {
|
||||
if(descMap.command=="07"){
|
||||
return [type: "update", value : "power (0B04) capability configured successfully"]
|
||||
}
|
||||
}
|
||||
|
||||
if (powerValue != "undefined"){
|
||||
return [type: "power", value : powerValue]
|
||||
}
|
||||
else {
|
||||
return [:]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ def parse(String description) {
|
||||
log.debug "parse($description)"
|
||||
def results = [:]
|
||||
|
||||
if (!isSupportedDescription(description) || description.startsWith("zone")) {
|
||||
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
|
||||
// Ignore this in favor of orientation-based state
|
||||
// results = parseSingleMessage(description)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ def parse(String description) {
|
||||
log.debug "parse($description)"
|
||||
def results = null
|
||||
|
||||
if (!isSupportedDescription(description) || description.startsWith("zone")) {
|
||||
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
|
||||
// Ignore this in favor of orientation-based state
|
||||
// results = parseSingleMessage(description)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Smartsense Moisture Sensor
|
||||
|
||||
Local Execution on V2 Hubs
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -23,11 +23,10 @@ Works with:
|
||||
|
||||
## 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
|
||||
A Category C2 moisture sensor with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Battery Specification
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
|
||||
metadata {
|
||||
definition(name: "SmartSense Moisture Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Refresh"
|
||||
@@ -29,10 +29,9 @@ metadata {
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-L", deviceJoinName: "Iris Smart Water Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor"
|
||||
}
|
||||
|
||||
@@ -43,10 +42,10 @@ metadata {
|
||||
preferences {
|
||||
section {
|
||||
image(name: 'educationalcontent', multiple: true, images: [
|
||||
"http://cdn.device-gse.smartthings.com/Moisture/Moisture1.png",
|
||||
"http://cdn.device-gse.smartthings.com/Moisture/Moisture2.png",
|
||||
"http://cdn.device-gse.smartthings.com/Moisture/Moisture3.png"
|
||||
])
|
||||
"http://cdn.device-gse.smartthings.com/Moisture/Moisture1.png",
|
||||
"http://cdn.device-gse.smartthings.com/Moisture/Moisture2.png",
|
||||
"http://cdn.device-gse.smartthings.com/Moisture/Moisture3.png"
|
||||
])
|
||||
}
|
||||
section {
|
||||
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"
|
||||
@@ -55,32 +54,32 @@ metadata {
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name: "water", type: "generic", width: 6, height: 4) {
|
||||
tileAttribute("device.water", key: "PRIMARY_CONTROL") {
|
||||
attributeState "dry", label: "Dry", icon: "st.alarm.water.dry", backgroundColor: "#ffffff"
|
||||
attributeState "wet", label: "Wet", icon: "st.alarm.water.wet", backgroundColor: "#53a7c0"
|
||||
multiAttributeTile(name:"water", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.water", key: "PRIMARY_CONTROL") {
|
||||
attributeState "dry", label: "Dry", icon:"st.alarm.water.dry", backgroundColor:"#ffffff"
|
||||
attributeState "wet", label: "Wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
|
||||
}
|
||||
}
|
||||
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"]
|
||||
]
|
||||
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: ""
|
||||
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"
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["water", "temperature"])
|
||||
main (["water", "temperature"])
|
||||
details(["water", "temperature", "battery", "refresh"])
|
||||
}
|
||||
}
|
||||
@@ -88,43 +87,117 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
// getEvent will handle temperature and humidity
|
||||
Map map = zigbee.getEvent(description)
|
||||
if (!map) {
|
||||
if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
} else {
|
||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||
if (descMap.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) {
|
||||
map = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
} else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) {
|
||||
if (descMap.data[0] == "00") {
|
||||
log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap"
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
} else {
|
||||
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
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) : [:]
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = zigbee.enrollResponse()
|
||||
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:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
(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))
|
||||
}
|
||||
|
||||
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) {
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
|
||||
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
def linkText = getLinkText(device)
|
||||
@@ -165,18 +238,40 @@ private Map getBatteryResult(rawValue) {
|
||||
return result
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText
|
||||
if ( temperatureScale == 'C' )
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
private Map getMoistureResult(value) {
|
||||
log.debug "water"
|
||||
def descriptionText
|
||||
if (value == "wet")
|
||||
descriptionText = '{{ device.displayName }} is wet'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} is dry'
|
||||
def descriptionText
|
||||
if ( value == "wet" )
|
||||
descriptionText = '{{ device.displayName }} is wet'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} is dry'
|
||||
return [
|
||||
name : 'water',
|
||||
value : value,
|
||||
descriptionText: descriptionText,
|
||||
translatable : true
|
||||
name: 'water',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
@@ -189,18 +284,59 @@ def ping() {
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Temperature and Battery"
|
||||
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
|
||||
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020)
|
||||
def refreshCmds = [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
||||
]
|
||||
|
||||
return refreshCmds + zigbee.enrollResponse()
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||
// 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: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Smartsense Motion Sensor
|
||||
|
||||
Local Execution on V2 Hubs
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -22,12 +22,10 @@ Works with:
|
||||
|
||||
## 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
|
||||
|
||||
A Category C2 motion sensor with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Battery Specification
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
|
||||
metadata {
|
||||
definition(name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Motion Sensor"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
@@ -45,10 +45,10 @@ metadata {
|
||||
preferences {
|
||||
section {
|
||||
image(name: 'educationalcontent', multiple: true, images: [
|
||||
"http://cdn.device-gse.smartthings.com/Motion/Motion1.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Motion/Motion2.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Motion/Motion3.jpg"
|
||||
])
|
||||
"http://cdn.device-gse.smartthings.com/Motion/Motion1.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Motion/Motion2.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Motion/Motion3.jpg"
|
||||
])
|
||||
}
|
||||
section {
|
||||
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"
|
||||
@@ -57,30 +57,30 @@ metadata {
|
||||
}
|
||||
|
||||
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"
|
||||
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"]
|
||||
]
|
||||
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: ""
|
||||
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"
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["motion", "temperature"])
|
||||
@@ -90,40 +90,116 @@ metadata {
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
Map map = zigbee.getEvent(description)
|
||||
if (!map) {
|
||||
if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
} else {
|
||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||
if (descMap?.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) {
|
||||
map = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
} else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) {
|
||||
if (descMap.data[0] == "00") {
|
||||
log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap"
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
} else {
|
||||
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
|
||||
}
|
||||
} else if (descMap.clusterInt == 0x0406 && descMap.attrInt == 0x0000) {
|
||||
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
||||
log.warn "Doing a read attr motion event"
|
||||
resultMap = getMotionResult(value)
|
||||
}
|
||||
}
|
||||
|
||||
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) : [:]
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = zigbee.enrollResponse()
|
||||
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:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
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
|
||||
|
||||
case 0x0406:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
log.debug 'motion'
|
||||
resultMap.name = 'motion'
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
(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) {
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
|
||||
@@ -131,6 +207,15 @@ private Map parseIasMessage(String description) {
|
||||
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
def linkText = getLinkText(device)
|
||||
@@ -170,14 +255,36 @@ private Map getBatteryResult(rawValue) {
|
||||
return result
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText
|
||||
if ( temperatureScale == 'C' )
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
private Map getMotionResult(value) {
|
||||
log.debug 'motion'
|
||||
String descriptionText = value == 'active' ? "{{ device.displayName }} detected motion" : "{{ device.displayName }} motion has stopped"
|
||||
return [
|
||||
name : 'motion',
|
||||
value : value,
|
||||
descriptionText: descriptionText,
|
||||
translatable : true
|
||||
name: 'motion',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
@@ -185,24 +292,64 @@ private Map getMotionResult(value) {
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
return zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) // Read the Battery Level
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
}
|
||||
|
||||
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"
|
||||
]
|
||||
|
||||
def refreshCmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
|
||||
zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000)
|
||||
|
||||
return refreshCmds + zigbee.enrollResponse()
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||
// 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: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
/**
|
||||
* 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
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
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"
|
||||
|
||||
}
|
||||
|
||||
preferences {
|
||||
section {
|
||||
image(name: 'educationalcontent', multiple: true, images: [
|
||||
"http://cdn.device-gse.smartthings.com/Motion/Motion1.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Motion/Motion2.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Motion/Motion3.jpg"
|
||||
])
|
||||
}
|
||||
section {
|
||||
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) : [:]
|
||||
|
||||
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:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
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
|
||||
|
||||
case 0x0406:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
log.debug 'motion'
|
||||
resultMap.name = 'motion'
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
(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) {
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
|
||||
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
|
||||
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [:]
|
||||
|
||||
def volts = rawValue / 10
|
||||
|
||||
if (!(rawValue == 0 || rawValue == 255)) {
|
||||
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.name = 'battery'
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText
|
||||
if ( temperatureScale == 'C' )
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
private Map getMotionResult(value) {
|
||||
log.debug 'motion'
|
||||
String descriptionText = value == 'active' ? "{{ device.displayName }} detected motion" : "{{ device.displayName }} motion has stopped"
|
||||
return [
|
||||
name: 'motion',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [
|
||||
"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() {
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -45,7 +45,7 @@ metadata {
|
||||
|
||||
def parse(String description) {
|
||||
def results = [:]
|
||||
if (description.startsWith("zone") || !isSupportedDescription(description)) {
|
||||
if (isZoneType19(description) || !isSupportedDescription(description)) {
|
||||
results = parseBasicMessage(description)
|
||||
}
|
||||
else if (isMotionStatusMessage(description)){
|
||||
@@ -57,7 +57,6 @@ def parse(String description) {
|
||||
|
||||
private Map parseBasicMessage(description) {
|
||||
def name = parseName(description)
|
||||
def results = [:]
|
||||
if (name != null) {
|
||||
def value = parseValue(description)
|
||||
def linkText = getLinkText(device)
|
||||
@@ -65,7 +64,7 @@ private Map parseBasicMessage(description) {
|
||||
def handlerName = value
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
|
||||
results = [
|
||||
def results = [
|
||||
name : name,
|
||||
value : value,
|
||||
linkText : linkText,
|
||||
@@ -74,6 +73,8 @@ private Map parseBasicMessage(description) {
|
||||
isStateChange : isStateChange,
|
||||
displayed : displayed(description, isStateChange)
|
||||
]
|
||||
} else {
|
||||
results = [:]
|
||||
}
|
||||
log.debug "Parse returned $results.descriptionText"
|
||||
return results
|
||||
@@ -87,12 +88,16 @@ private String parseName(String description) {
|
||||
}
|
||||
|
||||
private String parseValue(String description) {
|
||||
def zs = zigbee.parseZoneStatus(description)
|
||||
if (zs) {
|
||||
zs.isAlarm1Set() ? "active" : "inactive"
|
||||
} else {
|
||||
description
|
||||
if (isZoneType19(description)) {
|
||||
if (translateStatusZoneType19(description)) {
|
||||
return "active"
|
||||
}
|
||||
else {
|
||||
return "inactive"
|
||||
}
|
||||
}
|
||||
|
||||
description
|
||||
}
|
||||
|
||||
private parseDescriptionText(String linkText, String value, String description) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Smartsense Multi Sensor
|
||||
|
||||
Local Execution on V2 Hubs
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -26,11 +26,10 @@ Works with:
|
||||
|
||||
## 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
|
||||
A Category C2 multi sensor with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Battery Specification
|
||||
|
||||
|
||||
@@ -14,23 +14,22 @@
|
||||
* under the License.
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
import physicalgraph.zigbee.zcl.DataType
|
||||
|
||||
metadata {
|
||||
definition(name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Three Axis"
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Contact Sensor"
|
||||
capability "Acceleration Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Contact Sensor"
|
||||
capability "Acceleration Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Health Check"
|
||||
|
||||
command "enrollResponse"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
||||
command "enrollResponse"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor"
|
||||
@@ -58,11 +57,11 @@ metadata {
|
||||
preferences {
|
||||
section {
|
||||
image(name: 'educationalcontent', multiple: true, images: [
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi2.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi3.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi4.jpg"
|
||||
])
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi2.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi3.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi4.jpg"
|
||||
])
|
||||
}
|
||||
section {
|
||||
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"
|
||||
@@ -74,170 +73,211 @@ metadata {
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name: "status", type: "generic", width: 6, height: 4) {
|
||||
tileAttribute("device.status", key: "PRIMARY_CONTROL") {
|
||||
attributeState "open", label: 'Open', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
||||
attributeState "closed", label: 'Closed', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
attributeState "garage-open", label: 'Open', icon: "st.doors.garage.garage-open", backgroundColor: "#ffa81e"
|
||||
attributeState "garage-closed", label: 'Closed', icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821"
|
||||
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
||||
attributeState "open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||
attributeState "closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||
attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e"
|
||||
attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
|
||||
}
|
||||
}
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state("open", label: 'Open', icon: "st.contact.contact.open", backgroundColor: "#ffa81e")
|
||||
state("closed", label: 'Closed', icon: "st.contact.contact.closed", backgroundColor: "#79b821")
|
||||
state("open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
}
|
||||
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
||||
state("active", label: 'Active', icon: "st.motion.acceleration.active", backgroundColor: "#53a7c0")
|
||||
state("inactive", label: 'Inactive', icon: "st.motion.acceleration.inactive", backgroundColor: "#ffffff")
|
||||
state("active", label:'Active', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
||||
state("inactive", label:'Inactive', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("temperature", "device.temperature", 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"]
|
||||
]
|
||||
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: ""
|
||||
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"
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
|
||||
main(["status", "acceleration", "temperature"])
|
||||
details(["status", "acceleration", "temperature", "battery", "refresh"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def maps = []
|
||||
maps << zigbee.getEvent(description)
|
||||
if (!maps[0]) {
|
||||
maps = []
|
||||
if (description?.startsWith('zone status')) {
|
||||
maps += parseIasMessage(description)
|
||||
} else {
|
||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||
if (descMap?.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) {
|
||||
maps << getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
} else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) {
|
||||
if (descMap.data[0] == "00") {
|
||||
log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap"
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
} else {
|
||||
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
|
||||
}
|
||||
} else {
|
||||
|
||||
maps += handleAcceleration(descMap)
|
||||
}
|
||||
}
|
||||
} else if (maps[0].name == "temperature") {
|
||||
def map = maps[0]
|
||||
if (tempOffset) {
|
||||
map.value = (int) map.value + (int) tempOffset
|
||||
}
|
||||
map.descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C' : '{{ device.displayName }} was {{ value }}°F'
|
||||
map.translatable = true
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
def result = maps.inject([]) {acc, it ->
|
||||
if (it) {
|
||||
acc << createEvent(it)
|
||||
}
|
||||
}
|
||||
def result = map ? createEvent(map) : [:]
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = zigbee.enrollResponse()
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
result = parseReportAttributeMessage(description).each { createEvent(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private List<Map> handleAcceleration(descMap) {
|
||||
def result = []
|
||||
if (descMap.clusterInt == 0xFC02 && descMap.attrInt == 0x0010) {
|
||||
def value = descMap.value == "01" ? "active" : "inactive"
|
||||
log.debug "Acceleration $value"
|
||||
result << [
|
||||
name : "acceleration",
|
||||
value : value,
|
||||
descriptionText: "{{ device.displayName }} was $value",
|
||||
isStateChange : isStateChange(device, "acceleration", value),
|
||||
translatable : true
|
||||
]
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
log.debug cluster
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
}
|
||||
break
|
||||
|
||||
if (descMap.additionalAttrs) {
|
||||
result += parseAxis(descMap.additionalAttrs)
|
||||
case 0xFC02:
|
||||
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
|
||||
}
|
||||
} else if (descMap.clusterInt == 0xFC02 && descMap.attrInt == 0x0012) {
|
||||
def addAttrs = descMap.additionalAttrs
|
||||
addAttrs << ["attrInt": descMap.attrInt, "value": descMap.value]
|
||||
result += parseAxis(addAttrs)
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
|
||||
private List parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
|
||||
List result = []
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
result << getTemperatureResult(value)
|
||||
}
|
||||
else if (descMap.cluster == "FC02" && descMap.attrId == "0010") {
|
||||
if (descMap.value.size() == 32) {
|
||||
// value will look like 00ae29001403e2290013001629001201
|
||||
// breaking this apart and swapping byte order where appropriate, this breaks down to:
|
||||
// X (0x0012) = 0x0016
|
||||
// Y (0x0013) = 0x03E2
|
||||
// Z (0x0014) = 0x00AE
|
||||
// note that there is a known bug in that the x,y,z attributes are interpreted in the wrong order
|
||||
// this will be fixed in a future update
|
||||
def threeAxisAttributes = descMap.value[0..-9]
|
||||
result << parseAxis(threeAxisAttributes)
|
||||
descMap.value = descMap.value[-2..-1]
|
||||
}
|
||||
result << getAccelerationResult(descMap.value)
|
||||
}
|
||||
else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) {
|
||||
// The size is checked to ensure the attribute report contains X, Y and Z values
|
||||
// If all three axis are not included then the attribute report is ignored
|
||||
result << parseAxis(descMap.value)
|
||||
}
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
result << getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private List<Map> parseAxis(List<Map> attrData) {
|
||||
def results = []
|
||||
def x = hexToSignedInt(attrData.find { it.attrInt == 0x0012 }?.value)
|
||||
def y = hexToSignedInt(attrData.find { it.attrInt == 0x0013 }?.value)
|
||||
def z = hexToSignedInt(attrData.find { it.attrInt == 0x0014 }?.value)
|
||||
|
||||
def xyzResults = [:]
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
// This mapping matches the current behavior of the Device Handler for the Centralite sensors
|
||||
xyzResults.x = z
|
||||
xyzResults.y = y
|
||||
xyzResults.z = -x
|
||||
} else {
|
||||
// The axises reported by the Device Handler differ from the axises reported by the sensor
|
||||
// This may change in the future
|
||||
xyzResults.x = z
|
||||
xyzResults.y = x
|
||||
xyzResults.z = y
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
|
||||
log.debug "parseAxis -- ${xyzResults}"
|
||||
|
||||
if (garageSensor == "Yes")
|
||||
results += garageEvent(xyzResults.z)
|
||||
|
||||
def value = "${xyzResults.x},${xyzResults.y},${xyzResults.z}"
|
||||
results << [
|
||||
name : "threeAxis",
|
||||
value : value,
|
||||
linkText : getLinkText(device),
|
||||
descriptionText: "${getLinkText(device)} was ${value}",
|
||||
handlerName : name,
|
||||
isStateChange : isStateChange(device, "threeAxis", value),
|
||||
displayed : false
|
||||
]
|
||||
results
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private List<Map> parseIasMessage(String description) {
|
||||
private Map parseIasMessage(String description) {
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
List<Map> results = []
|
||||
Map resultMap = [:]
|
||||
|
||||
if (garageSensor != "Yes") {
|
||||
def value = zs.isAlarm1Set() ? 'open' : 'closed'
|
||||
log.debug "Contact: ${device.displayName} value = ${value}"
|
||||
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
||||
results << [name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true]
|
||||
results << [name: 'status', value: value, descriptionText: descriptionText, translatable: true]
|
||||
}
|
||||
if (garageSensor != "Yes"){
|
||||
resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
||||
}
|
||||
|
||||
return results
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "updated called"
|
||||
log.info "garage value : $garageSensor"
|
||||
if (garageSensor == "Yes") {
|
||||
def descriptionText = "Updating device to garage sensor"
|
||||
if (device.latestValue("status") == "open") {
|
||||
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
else if (device.latestValue("status") == "closed") {
|
||||
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
else {
|
||||
def descriptionText = "Updating device to open/close sensor"
|
||||
if (device.latestValue("status") == "garage-open") {
|
||||
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
else if (device.latestValue("status") == "garage-closed") {
|
||||
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
|
||||
@@ -276,24 +316,54 @@ private Map getBatteryResult(rawValue) {
|
||||
return result
|
||||
}
|
||||
|
||||
List<Map> garageEvent(zValue) {
|
||||
List<Map> results = []
|
||||
def absValue = zValue.abs()
|
||||
def contactValue = null
|
||||
def garageValue = null
|
||||
if (absValue > 900) {
|
||||
contactValue = 'closed'
|
||||
garageValue = 'garage-closed'
|
||||
} else if (absValue < 100) {
|
||||
contactValue = 'open'
|
||||
garageValue = 'garage-open'
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug "Temperature"
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
if (contactValue != null) {
|
||||
def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
||||
results << [name: 'contact', value: contactValue, descriptionText: descriptionText, displayed: false, translatable: true]
|
||||
results << [name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true]
|
||||
}
|
||||
results
|
||||
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C':
|
||||
'{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
private Map getContactResult(value) {
|
||||
log.debug "Contact: ${device.displayName} value = ${value}"
|
||||
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
|
||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
|
||||
private getAccelerationResult(numValue) {
|
||||
log.debug "Acceleration"
|
||||
def name = "acceleration"
|
||||
def value
|
||||
def descriptionText
|
||||
|
||||
if ( numValue.endsWith("1") ) {
|
||||
value = "active"
|
||||
descriptionText = '{{ device.displayName }} was active'
|
||||
} else {
|
||||
value = "inactive"
|
||||
descriptionText = '{{ device.displayName }} was inactive'
|
||||
}
|
||||
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
return [
|
||||
name: name,
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
isStateChange: isStateChange,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,21 +376,7 @@ def ping() {
|
||||
def refresh() {
|
||||
log.debug "Refreshing Values "
|
||||
|
||||
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
|
||||
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
|
||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode]) +
|
||||
zigbee.enrollResponse()
|
||||
|
||||
return refreshCmds
|
||||
}
|
||||
|
||||
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])
|
||||
|
||||
log.debug "Configuring Reporting"
|
||||
def configCmds = []
|
||||
def refreshCmds = []
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||
@@ -329,45 +385,81 @@ def configure() {
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
configCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
|
||||
configCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
|
||||
} else {
|
||||
// Write a motion threshold of 2 * .063g = .126g
|
||||
// Currently due to a Centralite firmware issue, this will cause a read attribute response that
|
||||
// indicates acceleration even when there isn't.
|
||||
configCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
||||
}
|
||||
|
||||
//Common refresh commands
|
||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
||||
zigbee.readAttribute(0x0001, 0x0020) +
|
||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
||||
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
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
|
||||
configCmds += zigbee.batteryConfig() +
|
||||
def configCmds = zigbee.batteryConfig() +
|
||||
zigbee.temperatureConfig(30, 300) +
|
||||
zigbee.configureReporting(0xFC02, 0x0010, DataType.BITMAP8, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0012, DataType.INT16, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0013, DataType.INT16, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0014, DataType.INT16, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
||||
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
||||
|
||||
return refresh() + configCmds
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "updated called"
|
||||
log.info "garage value : $garageSensor"
|
||||
if (garageSensor == "Yes") {
|
||||
def descriptionText = "Updating device to garage sensor"
|
||||
if (device.latestValue("status") == "open") {
|
||||
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true)
|
||||
} else if (device.latestValue("status") == "closed") {
|
||||
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
private getEndpointId() {
|
||||
new BigInteger(device.endpointId, 16).toString()
|
||||
}
|
||||
|
||||
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}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||
]
|
||||
}
|
||||
|
||||
private Map parseAxis(String description) {
|
||||
def z = hexToSignedInt(description[0..3])
|
||||
def y = hexToSignedInt(description[10..13])
|
||||
def x = hexToSignedInt(description[20..23])
|
||||
def xyzResults = [x: x, y: y, z: z]
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
// This mapping matches the current behavior of the Device Handler for the Centralite sensors
|
||||
xyzResults.x = z
|
||||
xyzResults.y = y
|
||||
xyzResults.z = -x
|
||||
} else {
|
||||
def descriptionText = "Updating device to open/close sensor"
|
||||
if (device.latestValue("status") == "garage-open") {
|
||||
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true)
|
||||
} else if (device.latestValue("status") == "garage-closed") {
|
||||
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
// The axises reported by the Device Handler differ from the axises reported by the sensor
|
||||
// This may change in the future
|
||||
xyzResults.x = z
|
||||
xyzResults.y = x
|
||||
xyzResults.z = y
|
||||
}
|
||||
|
||||
log.debug "parseAxis -- ${xyzResults}"
|
||||
|
||||
if (garageSensor == "Yes")
|
||||
garageEvent(xyzResults.z)
|
||||
|
||||
getXyzResult(xyzResults, description)
|
||||
}
|
||||
|
||||
private hexToSignedInt(hexVal) {
|
||||
@@ -375,6 +467,44 @@ private hexToSignedInt(hexVal) {
|
||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||
}
|
||||
|
||||
def garageEvent(zValue) {
|
||||
def absValue = zValue.abs()
|
||||
def contactValue = null
|
||||
def garageValue = null
|
||||
if (absValue>900) {
|
||||
contactValue = 'closed'
|
||||
garageValue = 'garage-closed'
|
||||
}
|
||||
else if (absValue < 100) {
|
||||
contactValue = 'open'
|
||||
garageValue = 'garage-open'
|
||||
}
|
||||
if (contactValue != null){
|
||||
def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' :'{{ device.displayName }} was closed'
|
||||
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false, translatable: true)
|
||||
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
|
||||
private Map getXyzResult(results, description) {
|
||||
def name = "threeAxis"
|
||||
def value = "${results.x},${results.y},${results.z}"
|
||||
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: name,
|
||||
isStateChange: isStateChange,
|
||||
displayed: false
|
||||
]
|
||||
}
|
||||
|
||||
private getManufacturerCode() {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
return "0x110A"
|
||||
@@ -386,3 +516,25 @@ private getManufacturerCode() {
|
||||
private hexToInt(value) {
|
||||
new BigInteger(value, 16)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ metadata {
|
||||
def parse(String description) {
|
||||
def results
|
||||
|
||||
if (!isSupportedDescription(description) || description.startsWith("zone")) {
|
||||
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
|
||||
results = parseSingleMessage(description)
|
||||
}
|
||||
else if (description == 'updated') {
|
||||
@@ -488,7 +488,12 @@ private String parseValue(String description) {
|
||||
if (!isSupportedDescription(description)) {
|
||||
return description
|
||||
}
|
||||
return zigbee.parseZoneStatus(description)?.isAlarm1Set() ? "open" : "closed"
|
||||
else if (zigbee.translateStatusZoneType19(description)) {
|
||||
return "open"
|
||||
}
|
||||
else {
|
||||
return "closed"
|
||||
}
|
||||
}
|
||||
|
||||
private parseDescriptionText(String linkText, String value, String description) {
|
||||
|
||||
@@ -0,0 +1,503 @@
|
||||
/**
|
||||
* 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
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Contact Sensor"
|
||||
capability "Acceleration Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Health Check"
|
||||
capability "Sensor"
|
||||
|
||||
command "enrollResponse"
|
||||
attribute "status", "string"
|
||||
}
|
||||
|
||||
preferences {
|
||||
section {
|
||||
image(name: 'educationalcontent', multiple: true, images: [
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi2.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi3.jpg",
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi4.jpg"
|
||||
])
|
||||
}
|
||||
section {
|
||||
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
|
||||
}
|
||||
section {
|
||||
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", description: "Tap to set", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
|
||||
}
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
||||
attributeState "open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||
attributeState "closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||
attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e"
|
||||
attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
|
||||
}
|
||||
}
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state("open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
}
|
||||
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
||||
state("active", label:'Active', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
||||
state("inactive", label:'Inactive', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("temperature", "device.temperature", 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(["status", "acceleration", "temperature"])
|
||||
details(["status", "acceleration", "temperature", "battery", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
result = parseReportAttributeMessage(description).each { createEvent(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
log.debug cluster
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
}
|
||||
break
|
||||
|
||||
case 0xFC02:
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
|
||||
private List parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
|
||||
List result = []
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
result << getTemperatureResult(value)
|
||||
}
|
||||
else if (descMap.cluster == "FC02" && descMap.attrId == "0010") {
|
||||
if (descMap.value.size() == 32) {
|
||||
// value will look like 00ae29001403e2290013001629001201
|
||||
// breaking this apart and swapping byte order where appropriate, this breaks down to:
|
||||
// X (0x0012) = 0x0016
|
||||
// Y (0x0013) = 0x03E2
|
||||
// Z (0x0014) = 0x00AE
|
||||
// note that there is a known bug in that the x,y,z attributes are interpreted in the wrong order
|
||||
// this will be fixed in a future update
|
||||
def threeAxisAttributes = descMap.value[0..-9]
|
||||
result << parseAxis(threeAxisAttributes)
|
||||
descMap.value = descMap.value[-2..-1]
|
||||
}
|
||||
result << getAccelerationResult(descMap.value)
|
||||
}
|
||||
else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) {
|
||||
// The size is checked to ensure the attribute report contains X, Y and Z values
|
||||
// If all three axis are not included then the attribute report is ignored
|
||||
result << parseAxis(descMap.value)
|
||||
}
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
result << getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
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) {
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
Map resultMap = [:]
|
||||
|
||||
if (garageSensor != "Yes"){
|
||||
resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "updated called"
|
||||
log.info "garage value : $garageSensor"
|
||||
if (garageSensor == "Yes") {
|
||||
def descriptionText = "Updating device to garage sensor"
|
||||
if (device.latestValue("status") == "open") {
|
||||
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
else if (device.latestValue("status") == "closed") {
|
||||
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
else {
|
||||
def descriptionText = "Updating device to open/close sensor"
|
||||
if (device.latestValue("status") == "garage-open") {
|
||||
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
else if (device.latestValue("status") == "garage-closed") {
|
||||
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [:]
|
||||
|
||||
def volts = rawValue / 10
|
||||
|
||||
if (!(rawValue == 0 || rawValue == 255)) {
|
||||
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.name = 'battery'
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug "Temperature"
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C':
|
||||
'{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
private Map getContactResult(value) {
|
||||
log.debug "Contact: ${device.displayName} value = ${value}"
|
||||
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
|
||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
|
||||
private getAccelerationResult(numValue) {
|
||||
log.debug "Acceleration"
|
||||
def name = "acceleration"
|
||||
def value
|
||||
def descriptionText
|
||||
|
||||
if ( numValue.endsWith("1") ) {
|
||||
value = "active"
|
||||
descriptionText = '{{ device.displayName }} was active'
|
||||
} else {
|
||||
value = "inactive"
|
||||
descriptionText = '{{ device.displayName }} was inactive'
|
||||
}
|
||||
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
return [
|
||||
name: name,
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
isStateChange: isStateChange,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 "
|
||||
|
||||
def refreshCmds = []
|
||||
|
||||
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])
|
||||
} else {
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
||||
}
|
||||
|
||||
//Common refresh commands
|
||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
||||
zigbee.readAttribute(0x0001, 0x0020) +
|
||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
||||
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
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])
|
||||
|
||||
return refresh() + configCmds
|
||||
}
|
||||
|
||||
private getEndpointId() {
|
||||
new BigInteger(device.endpointId, 16).toString()
|
||||
}
|
||||
|
||||
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}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||
]
|
||||
}
|
||||
|
||||
private Map parseAxis(String description) {
|
||||
def z = hexToSignedInt(description[0..3])
|
||||
def y = hexToSignedInt(description[10..13])
|
||||
def x = hexToSignedInt(description[20..23])
|
||||
def xyzResults = [x: x, y: y, z: z]
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
// This mapping matches the current behavior of the Device Handler for the Centralite sensors
|
||||
xyzResults.x = z
|
||||
xyzResults.y = y
|
||||
xyzResults.z = -x
|
||||
} else {
|
||||
// The axises reported by the Device Handler differ from the axises reported by the sensor
|
||||
// This may change in the future
|
||||
xyzResults.x = z
|
||||
xyzResults.y = x
|
||||
xyzResults.z = y
|
||||
}
|
||||
|
||||
log.debug "parseAxis -- ${xyzResults}"
|
||||
|
||||
if (garageSensor == "Yes")
|
||||
garageEvent(xyzResults.z)
|
||||
|
||||
getXyzResult(xyzResults, description)
|
||||
}
|
||||
|
||||
private hexToSignedInt(hexVal) {
|
||||
def unsignedVal = hexToInt(hexVal)
|
||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||
}
|
||||
|
||||
def garageEvent(zValue) {
|
||||
def absValue = zValue.abs()
|
||||
def contactValue = null
|
||||
def garageValue = null
|
||||
if (absValue>900) {
|
||||
contactValue = 'closed'
|
||||
garageValue = 'garage-closed'
|
||||
}
|
||||
else if (absValue < 100) {
|
||||
contactValue = 'open'
|
||||
garageValue = 'garage-open'
|
||||
}
|
||||
if (contactValue != null){
|
||||
def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' :'{{ device.displayName }} was closed'
|
||||
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false, translatable: true)
|
||||
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
|
||||
private Map getXyzResult(results, description) {
|
||||
def name = "threeAxis"
|
||||
def value = "${results.x},${results.y},${results.z}"
|
||||
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: name,
|
||||
isStateChange: isStateChange,
|
||||
displayed: false
|
||||
]
|
||||
}
|
||||
|
||||
private getManufacturerCode() {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
return "0x110A"
|
||||
} else {
|
||||
return "0x104E"
|
||||
}
|
||||
}
|
||||
|
||||
private hexToInt(value) {
|
||||
new BigInteger(value, 16)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# Smartsense Open/Closed Sensor
|
||||
|
||||
Local Execution on V2 Hubs
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -24,11 +24,10 @@ Works with:
|
||||
|
||||
## 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
|
||||
A Category C2 open/closed sensor with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Battery Specification
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition(name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Contact Sensor"
|
||||
@@ -43,77 +43,155 @@ metadata {
|
||||
}
|
||||
|
||||
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"
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
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"]
|
||||
]
|
||||
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: ""
|
||||
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"
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["contact", "temperature"])
|
||||
details(["contact", "temperature", "battery", "refresh"])
|
||||
main (["contact", "temperature"])
|
||||
details(["contact","temperature","battery","refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
Map map = zigbee.getEvent(description)
|
||||
if (!map) {
|
||||
if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
} else {
|
||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||
if (descMap?.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) {
|
||||
map = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
} else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) {
|
||||
if (descMap.data[0] == "00") {
|
||||
log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap"
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
} else {
|
||||
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
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) : [:]
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = zigbee.enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
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:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
(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 == "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) {
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -126,8 +204,8 @@ private Map getBatteryResult(rawValue) {
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
result.name = 'battery'
|
||||
@@ -136,14 +214,31 @@ private Map getBatteryResult(rawValue) {
|
||||
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,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
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
|
||||
name: 'contact',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
@@ -156,20 +251,61 @@ def ping() {
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Temperature and Battery"
|
||||
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
|
||||
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020)
|
||||
def refreshCmds = [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
||||
]
|
||||
|
||||
return refreshCmds + zigbee.enrollResponse()
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||
// 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: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
// 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
|
||||
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // 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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SmartSense Temp/Humidity Sensor
|
||||
|
||||
Local Execution on V2 Hubs
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -24,11 +24,10 @@ Works with:
|
||||
|
||||
## 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
|
||||
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Battery Specification
|
||||
|
||||
|
||||
@@ -13,10 +13,8 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.zcl.DataType
|
||||
|
||||
metadata {
|
||||
definition(name: "SmartSense Temp/Humidity Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Refresh"
|
||||
@@ -33,7 +31,7 @@ metadata {
|
||||
status 'H 45': 'catchall: 0104 FC45 01 01 0140 00 D9B9 00 04 C2DF 0A 01 0000218911'
|
||||
status 'H 57': 'catchall: 0104 FC45 01 01 0140 00 4E55 00 04 C2DF 0A 01 0000211316'
|
||||
status 'H 53': 'catchall: 0104 FC45 01 01 0140 00 20CD 00 04 C2DF 0A 01 0000219814'
|
||||
status 'H 43': 'read attr - raw: BF7601FC450C00000021A410, dni: BF76, endpoint: 01, cluster: FC45, size: 0C, attrId: 0000, result: success, encoding: 21, value: 10a4'
|
||||
status 'H 43': 'read attr - raw: BF7601FC450C00000021A410, dni: BF76, endpoint: 01, cluster: FC45, size: 0C, attrId: 0000, result: success, encoding: 21, value: 10a4'
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -42,28 +40,28 @@ metadata {
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name: "temperature", type: "generic", width: 6, height: 4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState "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"]
|
||||
]
|
||||
multiAttributeTile(name:"temperature", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState "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("humidity", "device.humidity", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "humidity", label: '${currentValue}% humidity', unit: ""
|
||||
state "humidity", label:'${currentValue}% humidity', unit:""
|
||||
}
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label: '${currentValue}% battery'
|
||||
state "battery", label:'${currentValue}% battery'
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action: "refresh.refresh", icon: "st.secondary.refresh"
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main "temperature", "humidity"
|
||||
@@ -74,31 +72,142 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
// getEvent will handle temperature and humidity
|
||||
Map map = zigbee.getEvent(description)
|
||||
if (!map) {
|
||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||
if (descMap.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) {
|
||||
map = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
} else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) {
|
||||
if (descMap.data[0] == "00") {
|
||||
log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap"
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
} else {
|
||||
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
|
||||
}
|
||||
}
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
map = parseReportAttributeMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
return map ? createEvent(map) : [:]
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
(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 == "FC45" && descMap.attrId == "0000") {
|
||||
def value = getReportAttributeHumidity(descMap.value)
|
||||
resultMap = getHumidityResult(value)
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def getReportAttributeHumidity(String value) {
|
||||
def humidity = null
|
||||
if (value?.trim()) {
|
||||
try {
|
||||
// value is hex with no decimal
|
||||
def pct = Integer.parseInt(value.trim(), 16) / 100
|
||||
humidity = String.format('%.0f', pct)
|
||||
} catch(NumberFormatException nfe) {
|
||||
log.debug "Error converting $value to humidity"
|
||||
}
|
||||
}
|
||||
return humidity
|
||||
}
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
else if (description?.startsWith('humidity: ')) {
|
||||
def pct = (description - "humidity: " - "%").trim()
|
||||
if (pct.isNumber()) {
|
||||
def value = Math.round(new BigDecimal(pct)).toString()
|
||||
resultMap = getHumidityResult(value)
|
||||
} else {
|
||||
log.error "invalid humidity: ${pct}"
|
||||
}
|
||||
}
|
||||
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 = [:]
|
||||
def result = [:]
|
||||
|
||||
def volts = rawValue / 10
|
||||
if (!(rawValue == 0 || rawValue == 255)) {
|
||||
@@ -117,32 +226,78 @@ private Map getBatteryResult(rawValue) {
|
||||
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,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
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(0x0001, 0x0020) // Read the Battery Level
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
def refresh()
|
||||
{
|
||||
log.debug "refresh temperature, humidity, and battery"
|
||||
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware
|
||||
zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
|
||||
zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
|
||||
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
|
||||
zigbee.configureReporting(0xFC45, 0x0000, DataType.INT16, 30, 3600, 100) +
|
||||
zigbee.batteryConfig() +
|
||||
zigbee.temperatureConfig(30, 300)
|
||||
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
|
||||
zigbee.readAttribute(0x0402, 0x0000) +
|
||||
zigbee.readAttribute(0x0001, 0x0020)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||
// 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: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
def humidityConfigCmds = [
|
||||
"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()
|
||||
return refresh() + humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ metadata {
|
||||
def parse(String description) {
|
||||
def results
|
||||
|
||||
if (!isSupportedDescription(description) || description.startsWith("zone")) {
|
||||
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
|
||||
// Ignore this in favor of orientation-based state
|
||||
// results = parseSingleMessage(description)
|
||||
}
|
||||
|
||||
@@ -15,10 +15,9 @@ metadata {
|
||||
definition (name: "Simulated Minimote", namespace: "smartthings/testing", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Button"
|
||||
capability "Holdable Button"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
|
||||
|
||||
command "push1"
|
||||
command "push2"
|
||||
command "push3"
|
||||
@@ -46,42 +45,42 @@ metadata {
|
||||
}
|
||||
standardTile("push1", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||
state "default", label: "Push 1", backgroundColor: "#ffffff", action: "push1"
|
||||
}
|
||||
}
|
||||
standardTile("push2", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||
state "default", label: "Push 2", backgroundColor: "#ffffff", action: "push2"
|
||||
}
|
||||
}
|
||||
standardTile("push3", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||
state "default", label: "Push 3", backgroundColor: "#ffffff", action: "push3"
|
||||
}
|
||||
}
|
||||
standardTile("push4", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||
state "default", label: "Push 4", backgroundColor: "#ffffff", action: "push4"
|
||||
}
|
||||
}
|
||||
standardTile("dummy1", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||
state "default", label: " ", backgroundColor: "#ffffff", action: "push4"
|
||||
}
|
||||
}
|
||||
standardTile("hold1", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||
state "default", label: "Hold 1", backgroundColor: "#ffffff", action: "hold1"
|
||||
}
|
||||
}
|
||||
standardTile("hold2", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||
state "default", label: "Hold 2", backgroundColor: "#ffffff", action: "hold2"
|
||||
}
|
||||
}
|
||||
standardTile("dummy2", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||
state "default", label: " ", backgroundColor: "#ffffff", action: "push4"
|
||||
}
|
||||
}
|
||||
standardTile("hold3", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||
state "default", label: "Hold 3", backgroundColor: "#ffffff", action: "hold3"
|
||||
}
|
||||
}
|
||||
standardTile("hold4", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||
state "default", label: "Hold 4", backgroundColor: "#ffffff", action: "hold4"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
main "button"
|
||||
details(["push1","push2","button","push3","push4","dummy1","hold1","hold2","dummy2","hold3","hold4"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
def push1() {
|
||||
@@ -126,15 +125,3 @@ private hold(button) {
|
||||
sendEvent(name: "button", value: "held", data: [buttonNumber: button], descriptionText: "$device.displayName button $button was held", isStateChange: true)
|
||||
}
|
||||
|
||||
|
||||
def installed() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
sendEvent(name: "numberOfButtons", value: 4)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
|
||||
|
||||
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
|
||||
capability "Switch"
|
||||
capability "Relay Switch"
|
||||
@@ -39,7 +39,9 @@ metadata {
|
||||
}
|
||||
}
|
||||
|
||||
def parse(description) {
|
||||
def parse(String description) {
|
||||
def pair = description.split(":")
|
||||
createEvent(name: pair[0].trim(), value: pair[1].trim())
|
||||
}
|
||||
|
||||
def on() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tyco Door Window Sensor
|
||||
|
||||
Cloud Execution
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -23,11 +23,10 @@ Works with:
|
||||
|
||||
## Device Health
|
||||
|
||||
Tyco Door Window Sensor with reporting interval of 5 mins.
|
||||
Contact sensor with maxReportTime 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
|
||||
Check-in interval = 12 min
|
||||
|
||||
## Battery Specification
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ def parse(String description) {
|
||||
def bodyString = msg.body
|
||||
if (bodyString) {
|
||||
unschedule("setOffline")
|
||||
def body = new XmlSlurper().parseText(bodyString.replaceAll("[^\\x20-\\x7e]", ""))
|
||||
def body = new XmlSlurper().parseText(bodyString)
|
||||
|
||||
if (body?.property?.TimeSyncRequest?.text()) {
|
||||
log.trace "Got TimeSyncRequest"
|
||||
|
||||
@@ -78,7 +78,7 @@ def parse(String description) {
|
||||
def bodyString = msg.body
|
||||
if (bodyString) {
|
||||
unschedule("setOffline")
|
||||
def body = new XmlSlurper().parseText(bodyString.replaceAll("[^\\x20-\\x7e]", ""))
|
||||
def body = new XmlSlurper().parseText(bodyString)
|
||||
if (body?.property?.TimeSyncRequest?.text()) {
|
||||
log.trace "Got TimeSyncRequest"
|
||||
result << timeSyncResponse()
|
||||
|
||||
@@ -84,7 +84,7 @@ def parse(String description) {
|
||||
def bodyString = msg.body
|
||||
if (bodyString) {
|
||||
unschedule("setOffline")
|
||||
def body = new XmlSlurper().parseText(bodyString.replaceAll("[^\\x20-\\x7e]", ""))
|
||||
def body = new XmlSlurper().parseText(bodyString)
|
||||
if (body?.property?.TimeSyncRequest?.text()) {
|
||||
log.trace "Got TimeSyncRequest"
|
||||
result << timeSyncResponse()
|
||||
@@ -208,7 +208,7 @@ def subscribe(ip, port) {
|
||||
def existingIp = getDataValue("ip")
|
||||
def existingPort = getDataValue("port")
|
||||
if (ip && ip != existingIp) {
|
||||
log.debug "Updating ip from $existingIp to $ip"
|
||||
log.debug "Updating ip from $existingIp to $ip"
|
||||
updateDataValue("ip", ip)
|
||||
def ipvalue = convertHexToIP(getDataValue("ip"))
|
||||
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
|
||||
@@ -291,4 +291,4 @@ User-Agent: CyberGarage-HTTP/1.0
|
||||
</u:GetBinaryState>
|
||||
</s:Body>
|
||||
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
|
||||
}
|
||||
}
|
||||
@@ -13,14 +13,12 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.zcl.DataType
|
||||
|
||||
metadata {
|
||||
definition (name: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") {
|
||||
capability "Actuator"
|
||||
capability "Battery"
|
||||
capability "Button"
|
||||
capability "Holdable Button"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
@@ -29,8 +27,8 @@ metadata {
|
||||
|
||||
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"
|
||||
//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"
|
||||
}
|
||||
|
||||
simulator {}
|
||||
@@ -83,7 +81,7 @@ def parse(String description) {
|
||||
def result = event ? createEvent(event) : []
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = zigbee.enrollResponse()
|
||||
List cmds = enrollResponse()
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
@@ -118,38 +116,15 @@ private Map getBatteryResult(rawValue) {
|
||||
private Map parseNonIasButtonMessage(Map descMap){
|
||||
def buttonState = ""
|
||||
def buttonNumber = 0
|
||||
if ((device.getDataValue("model") == "3460-L") &&(descMap.clusterInt == 0x0006)) {
|
||||
if (descMap.commandInt == 1) {
|
||||
if (((device.getDataValue("model") == "3460-L") || (device.getDataValue("model") == "3450-L"))
|
||||
&&(descMap.clusterInt == 0x0006)) {
|
||||
if (descMap.command == "01") {
|
||||
getButtonResult("press")
|
||||
}
|
||||
else if (descMap.commandInt == 0) {
|
||||
else if (descMap.command == "00") {
|
||||
getButtonResult("release")
|
||||
}
|
||||
}
|
||||
else if ((device.getDataValue("model") == "3450-L") && (descMap.clusterInt == 0x0006)) {
|
||||
if (descMap.commandInt == 1) {
|
||||
getButtonResult("press")
|
||||
}
|
||||
else if (descMap.commandInt == 0) {
|
||||
def button = 1
|
||||
switch(descMap.sourceEndpoint) {
|
||||
case "01":
|
||||
button = 4
|
||||
break
|
||||
case "02":
|
||||
button = 3
|
||||
break
|
||||
case "03":
|
||||
button = 1
|
||||
break
|
||||
case "04":
|
||||
button = 2
|
||||
break
|
||||
}
|
||||
|
||||
getButtonResult("release", button)
|
||||
}
|
||||
}
|
||||
else if (descMap.clusterInt == 0x0006) {
|
||||
buttonState = "pushed"
|
||||
if (descMap.command == "01") {
|
||||
@@ -184,7 +159,7 @@ private Map parseNonIasButtonMessage(Map descMap){
|
||||
def refresh() {
|
||||
log.debug "Refreshing Battery"
|
||||
|
||||
return zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x20) +
|
||||
return zigbee.readAttribute(0x0001, 0x20) +
|
||||
zigbee.enrollResponse()
|
||||
}
|
||||
|
||||
@@ -201,9 +176,9 @@ def configure() {
|
||||
}
|
||||
return zigbee.onOffConfig() +
|
||||
zigbee.levelConfig() +
|
||||
zigbee.configureReporting(zigbee.POWER_CONFIGURATION_CLUSTER, 0x20, DataType.UINT8, 30, 21600, 0x01) +
|
||||
zigbee.configureReporting(0x0001, 0x20, 0x20, 30, 21600, 0x01) +
|
||||
zigbee.enrollResponse() +
|
||||
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x20) +
|
||||
zigbee.readAttribute(0x0001, 0x20) +
|
||||
cmds
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GE Plug-In/In-Wall Smart Dimmer (ZigBee)
|
||||
|
||||
Cloud Execution
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -23,15 +23,14 @@ Works with:
|
||||
* **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
|
||||
* **Light** - indicates that the device belongs to Light category.
|
||||
|
||||
## 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
|
||||
|
||||
A Zigbee dimmer with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
It then enrolls the device with updated checkInterval i.e. 12 mins
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702"
|
||||
@@ -98,7 +97,7 @@ def on() {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value) + (value?.toInteger() > 0 ? zigbee.on() : [])
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,8 +114,8 @@ def refresh() {
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
|
||||
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||
// 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: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
refresh()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Zigbee Dimmer
|
||||
|
||||
Cloud Execution
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -24,11 +24,11 @@ Works with:
|
||||
|
||||
## 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
|
||||
|
||||
A Zigbee dimmer with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
It then enrolls the device with updated checkInterval i.e. 12 mins
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -20,19 +20,12 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart A19 Soft White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY PAR38 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart PAR38 Soft White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart BR30 Soft White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E11-G13", deviceJoinName: "Sengled Element Classic"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL6HD", deviceJoinName: "Leviton Dimmer Switch"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL3HL", deviceJoinName: "Leviton Lumina RF Plug-In Dimmer"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL1KD", deviceJoinName: "Leviton Lumina RF Dimmer Switch"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
@@ -94,13 +87,7 @@ def on() {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
def additionalCmds = []
|
||||
if (device.getDataValue("model") == "iQBR30" && value.toInteger() > 0) { // Handle iQ bulb not following spec
|
||||
additionalCmds = zigbee.on()
|
||||
} else if (device.getDataValue("manufacturer") == "MRVL") { // Handle marvel stack not reporting
|
||||
additionalCmds = refresh()
|
||||
}
|
||||
zigbee.setLevel(value) + additionalCmds
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
@@ -110,15 +97,15 @@ def ping() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + 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)
|
||||
// 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: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
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
|
||||
refresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 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"
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorTemperature", label: '${currentValue} K'
|
||||
}
|
||||
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", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
//Globals
|
||||
private getATTRIBUTE_HUE() { 0x0000 }
|
||||
private getATTRIBUTE_SATURATION() { 0x0001 }
|
||||
private getHUE_COMMAND() { 0x00 }
|
||||
private getSATURATION_COMMAND() { 0x03 }
|
||||
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
|
||||
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def finalResult = zigbee.getEvent(description)
|
||||
if (finalResult) {
|
||||
log.debug finalResult
|
||||
sendEvent(finalResult)
|
||||
}
|
||||
else {
|
||||
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
|
||||
log.trace "zigbeeMap : $zigbeeMap"
|
||||
|
||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
||||
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
|
||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
||||
}
|
||||
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "saturation", value: saturationValue, displayed:false)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.info "DID NOT PARSE MESSAGE for description : $description"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on() + ["delay 1500"] + zigbee.onOffRefresh()
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off() + ["delay 1500"] + zigbee.onOffRefresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
refreshAttributes() + configureAttributes()
|
||||
}
|
||||
|
||||
def poll() {
|
||||
refreshAttributes()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
configureAttributes() + refreshAttributes()
|
||||
}
|
||||
|
||||
def configureAttributes() {
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def refreshAttributes() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
zigbee.setColorTemperature(value) + ["delay 1500"] + zigbee.colorTemperatureRefresh()
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
log.trace "setColor($value)"
|
||||
zigbee.on() + setHue(value.hue) + ["delay 300"] + setSaturation(value.saturation) + ["delay 2000"] + refreshAttributes()
|
||||
}
|
||||
|
||||
def setHue(value) {
|
||||
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) //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 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) //payload-> sat value, transition time
|
||||
}
|
||||
@@ -13,8 +13,6 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.zcl.DataType
|
||||
|
||||
metadata {
|
||||
definition (name: "ZigBee Lock", namespace: "smartthings", author: "SmartThings")
|
||||
{
|
||||
@@ -73,6 +71,9 @@ private getDOORLOCK_CMD_UNLOCK_DOOR() { 0x01 }
|
||||
private getDOORLOCK_ATTR_LOCKSTATE() { 0x0000 }
|
||||
private getPOWER_ATTR_BATTERY_PERCENTAGE_REMAINING() { 0x0021 }
|
||||
|
||||
private getTYPE_U8() { 0x20 }
|
||||
private getTYPE_ENUM8() { 0x30 }
|
||||
|
||||
// Public methods
|
||||
def installed() {
|
||||
log.trace "installed()"
|
||||
@@ -85,9 +86,9 @@ def uninstalled() {
|
||||
def configure() {
|
||||
def cmds =
|
||||
zigbee.configureReporting(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE,
|
||||
DataType.ENUM8, 0, 3600, null) +
|
||||
TYPE_ENUM8, 0, 3600, null) +
|
||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,
|
||||
DataType.UINT8, 600, 21600, 0x01)
|
||||
TYPE_U8, 600, 21600, 0x01)
|
||||
log.info "configure() --- cmds: $cmds"
|
||||
return refresh() + cmds // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*
|
||||
* This DTH should serve as the generic DTH to handle RGB ZigBee HA devices (For color bulbs with no color temperature)
|
||||
*/
|
||||
import physicalgraph.zigbee.zcl.DataType
|
||||
|
||||
metadata {
|
||||
definition (name: "ZigBee RGB Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -28,10 +27,9 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB", deviceJoinName: "SYLVANIA Smart 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: "SYLVANIA Smart Gardenspot mini RGB"
|
||||
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
|
||||
@@ -123,7 +121,7 @@ def ping() {
|
||||
}
|
||||
|
||||
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, DataType.UINT8, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, DataType.UINT8, 1, 3600, 0x01)
|
||||
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() {
|
||||
@@ -133,7 +131,7 @@ def configure() {
|
||||
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, DataType.UINT8, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, DataType.UINT8, 1, 3600, 0x01) + 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) + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# OSRAM LIGHTIFY LED RGBW Bulb
|
||||
|
||||
Cloud Execution
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -27,10 +27,11 @@ Works with:
|
||||
|
||||
## Device Health
|
||||
|
||||
OSRAM LIGHTIFY LED RGBW Bulb 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`
|
||||
A Category C6 OSRAM LIGHTIFY LED RGBW Bulb with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
* __12min__ checkInterval
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*
|
||||
* This DTH should serve as the generic DTH to handle RGBW ZigBee HA devices
|
||||
*/
|
||||
import physicalgraph.zigbee.zcl.DataType
|
||||
|
||||
metadata {
|
||||
definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -29,17 +28,15 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
attribute "colorName", "string"
|
||||
command "setGenericName"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "SYLVANIA Smart Flex RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY Flex RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "SYLVANIA Smart A19 RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR RGBW", deviceJoinName: "SYLVANIA Smart BR30 RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT RGBW", deviceJoinName: "SYLVANIA Smart RT5/6 RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY FLEX OUTDOOR RGBW", deviceJoinName: "SYLVANIA Smart Outdoor RGBW Flex"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED A19 RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR RGBW", deviceJoinName: "OSRAM LIGHTIFY LED BR30 RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT RGBW", deviceJoinName: "OSRAM LIGHTIFY LED RT 5/6 RGBW"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
@@ -141,14 +138,14 @@ def ping() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, DataType.UINT8, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, DataType.UINT8, 1, 3600, 0x01)
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||
// 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: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
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
|
||||
refresh()
|
||||
|
||||
@@ -21,7 +21,6 @@ metadata {
|
||||
capability "Sensor"
|
||||
capability "Switch"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0B04"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
|
||||
|
||||
@@ -22,8 +22,6 @@ metadata {
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "0003, 0006, 0019, 0406", manufacturer: "Leviton", model: "ZSS-10", deviceJoinName: "Leviton Switch"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "000A", manufacturer: "HAI", model: "65A21-1", deviceJoinName: "Leviton Wireless Load Control Module-30amp"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL15A", deviceJoinName: "Leviton Lumina RF Plug-In Appliance Module"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL15S", deviceJoinName: "Leviton Lumina RF Switch"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.zcl.DataType
|
||||
|
||||
metadata {
|
||||
definition (name: "ZigBee Valve", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -67,6 +66,8 @@ private getCLUSTER_BASIC() { 0x0000 }
|
||||
private getBASIC_ATTR_POWER_SOURCE() { 0x0007 }
|
||||
private getCLUSTER_POWER() { 0x0001 }
|
||||
private getPOWER_ATTR_BATTERY_PERCENTAGE_REMAINING() { 0x0021 }
|
||||
private getTYPE_U8() { 0x20 }
|
||||
private getTYPE_ENUM8() { 0x30 }
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
@@ -127,8 +128,8 @@ def refresh() {
|
||||
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
|
||||
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) +
|
||||
zigbee.onOffConfig() +
|
||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, DataType.UINT8, 600, 21600, 1) +
|
||||
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, DataType.ENUM8, 5, 21600, 1)
|
||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 1) +
|
||||
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, TYPE_ENUM8, 5, 21600, 1)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ZigBee White Color Temperature Bulb
|
||||
|
||||
Cloud Execution
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -25,10 +25,11 @@ Works with:
|
||||
|
||||
## Device Health
|
||||
|
||||
Zigbee Bulb 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
|
||||
Zigbee Bulb with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
It then enrolls the device with updated checkInterval i.e. 12 mins
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -26,19 +26,17 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Light"
|
||||
|
||||
attribute "colorName", "string"
|
||||
command "setGenericName"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04", outClusters: "0019"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "SYLVANIA Smart BR30 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "SYLVANIA Smart RT5/6 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "SYLVANIA Smart A19 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY RT5/6 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-A19NAE26", deviceJoinName: "Sengled Element plus"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
@@ -85,24 +83,105 @@ def parse(String description) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
def cluster = zigbee.parse(description)
|
||||
Map bindingTable = parseBindingTableResponse(description)
|
||||
if (bindingTable) {
|
||||
List<String> cmds = []
|
||||
bindingTable.table_entries.inject(cmds) { acc, entry ->
|
||||
// The binding entry is not for our hub and should be deleted
|
||||
if (entry["dstAddr"] != zigbeeEui) {
|
||||
acc.addAll(removeBinding(entry.clusterId, entry.srcAddr, entry.srcEndpoint, entry.dstAddr, entry.dstEndpoint))
|
||||
}
|
||||
acc
|
||||
}
|
||||
// There are more entries that we haven't examined yet
|
||||
if (bindingTable.numTableEntries > bindingTable.startIndex + bindingTable.numEntriesReturned) {
|
||||
def startPos
|
||||
if (cmds) {
|
||||
log.warn "Removing binding entries for other devices: $cmds"
|
||||
// Since we are removing some entries, we should start in the same spot as we just read since values
|
||||
// will fill in the newly vacated spots
|
||||
startPos = bindingTable.startIndex
|
||||
} else {
|
||||
// Since we aren't removing anything we move forward to the next set of table entries
|
||||
startPos = bindingTable.startIndex + bindingTable.numEntriesReturned
|
||||
}
|
||||
cmds.addAll(requestBindingTable(startPos))
|
||||
}
|
||||
sendHubCommand(cmds.collect { it ->
|
||||
new physicalgraph.device.HubAction(it)
|
||||
}, 2000)
|
||||
} 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])
|
||||
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 "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug "${cluster}"
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug "${cluster}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def parseBindingTableResponse(description) {
|
||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||
if (descMap["clusterInt"] == 0x8033) {
|
||||
def header_field_lengths = ["transactionSeqNo": 1, "status": 1, "numTableEntries": 1, "startIndex": 1, "numEntriesReturned": 1]
|
||||
def field_values = [:]
|
||||
def data = descMap["data"]
|
||||
header_field_lengths.each { k, v ->
|
||||
field_values[k] = Integer.parseInt(data.take(v).join(""), 16);
|
||||
data = data.drop(v);
|
||||
}
|
||||
|
||||
List<Map> table = []
|
||||
if (field_values.numEntriesReturned) {
|
||||
def table_entry_lengths = ["srcAddr": 8, "srcEndpoint": 1, "clusterId": 2, "dstAddrMode": 1]
|
||||
for (def i : 0..(field_values.numEntriesReturned - 1)) {
|
||||
def entryMap = [:]
|
||||
table_entry_lengths.each { k, v ->
|
||||
def val = data.take(v).reverse().join("")
|
||||
entryMap[k] = val.length() < 8 ? Integer.parseInt(val, 16) : val
|
||||
data = data.drop(v)
|
||||
}
|
||||
|
||||
switch (entryMap.dstAddrMode) {
|
||||
case 0x01:
|
||||
entryMap["dstAddr"] = data.take(2).reverse().join("")
|
||||
data = data.drop(2)
|
||||
break
|
||||
case 0x03:
|
||||
entryMap["dstAddr"] = data.take(8).reverse().join("")
|
||||
data = data.drop(8)
|
||||
entryMap["dstEndpoint"] = Integer.parseInt(data.take(1).join(""), 16)
|
||||
data = data.drop(1)
|
||||
break
|
||||
}
|
||||
table << entryMap
|
||||
}
|
||||
}
|
||||
field_values["table_entries"] = table
|
||||
return field_values
|
||||
}
|
||||
return [:]
|
||||
}
|
||||
|
||||
def requestBindingTable(startPos=0) {
|
||||
return ["zdo mgmt-bind 0x${zigbee.deviceNetworkId} $startPos"]
|
||||
}
|
||||
|
||||
def removeBinding(cluster, srcAddr, srcEndpoint, destAddr, destEndpoint) {
|
||||
return ["zdo unbind unicast 0x${zigbee.deviceNetworkId} {${srcAddr}} $srcEndpoint $cluster {${destAddr}} $destEndpoint"]
|
||||
}
|
||||
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
@@ -128,12 +207,11 @@ def refresh() {
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||
// 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: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
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
|
||||
refresh()
|
||||
refresh() + requestBindingTable(0) + ["delay 2000"]
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
|
||||
@@ -89,7 +89,7 @@ def on() {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
@@ -127,8 +127,8 @@ def configureHealthCheck() {
|
||||
|
||||
def configure() {
|
||||
log.debug "configure()"
|
||||
configureHealthCheck()
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
configureHealthCheck()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.zcl.DataType
|
||||
|
||||
metadata {
|
||||
definition (name: "ZLL RGB Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -108,7 +107,7 @@ def configure() {
|
||||
}
|
||||
|
||||
def configureAttributes() {
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, DataType.UINT8, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, DataType.UINT8, 1, 3600, 0x01)
|
||||
zigbee.onOffConfig() + 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 refreshAttributes() {
|
||||
@@ -116,7 +115,7 @@ def refreshAttributes() {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.zcl.DataType
|
||||
|
||||
metadata {
|
||||
definition (name: "ZLL RGBW Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -28,10 +27,6 @@ metadata {
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000", outClusters: "0019"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000", outClusters: "0019", "manufacturer":"OSRAM", "model":"Classic A60 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 RGBW"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "PAR 16 50 RGBW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY RGBW PAR 16 50"
|
||||
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY Flex RGBW"
|
||||
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenpole RGBW-Lightify", deviceJoinName: "OSRAM LIGHTIFY Gardenpole RGBW"
|
||||
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Outdoor Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY Outdoor Flex RGBW"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
@@ -124,7 +119,7 @@ def configure() {
|
||||
}
|
||||
|
||||
def configureAttributes() {
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, DataType.UINT8, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, DataType.UINT8, 1, 3600, 0x01)
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def refreshAttributes() {
|
||||
@@ -136,7 +131,7 @@ def setColorTemperature(value) {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
|
||||
@@ -32,7 +32,6 @@ metadata {
|
||||
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, 0B04, FC0F", outClusters: "0019", "manufacturer":"OSRAM", "model":"Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 Tunable White"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, FC0F", outClusters: "0019", "manufacturer":"OSRAM", "model":"PAR16 50 TW", deviceJoinName: "OSRAM LIGHTIFY LED PAR16 50 Tunable White"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
@@ -90,7 +89,7 @@ def on() {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
# Z-wave Dimmer Switch Generic
|
||||
# Z-wave Dimmer
|
||||
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-plug-in-lamp-dimmer-module)
|
||||
* [Leviton Universal Dimmer (DZMX1-LZ)](https://www.smartthings.com/works-with-smartthings/switches-and-dimmers/leviton-universal-dimmer)
|
||||
* [Leviton 1000W Incandescent Dimmer](https://www.smartthings.com/works-with-smartthings/leviton/leviton-1000w-incandescent-dimmer)
|
||||
* [Leviton 600W Incandescent Dimmer](https://www.smartthings.com/works-with-smartthings/leviton/leviton-600w-incandescent-dimmer)
|
||||
* [Enerwave In-Wall Dimmer](https://www.smartthings.com/works-with-smartthings/enerwave/enerwave-in-wall-dimmer-zw500d)
|
||||
* [Leviton 3-Speed Fan Controller](https://www.smartthings.com/works-with-smartthings/leviton/leviton-3-speed-fan-controller)
|
||||
* [Leviton Magnetic Low Voltage Dimmer](https://www.smartthings.com/works-with-smartthings/leviton/leviton-magnetic-low-voltage-dimmer)
|
||||
* [Remotec Technology Plug-In Dimmer](https://www.smartthings.com/works-with-smartthings/remotec-technology/remotec-technology-plug-in-dimmer)
|
||||
|
||||
## Table of contents
|
||||
|
||||
@@ -38,17 +32,10 @@ Not to mention after going OFFLINE when the device is plugged back in, it might
|
||||
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:
|
||||
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||
* [Leviton Universal Dimmer (DZMX1-LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||
* [Leviton 1000W Incandescent Dimmer Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||
* [Leviton 600W Incandescent Dimmer Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||
* [Leviton 3-Speed Fan Controller Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||
* [Enerwave In-Wall Dimmer Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/204854176-How-to-connect-Enerwave-switches-and-dimmers)
|
||||
* [Remotec Technology Plug-In Dimmer Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202295150-Remotec-Technology-Plug-In-Dimmer-ZDS-100-)
|
||||
* [Leviton Universal Dimmer (DZMX1-LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||
@@ -20,17 +20,10 @@ metadata {
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Light"
|
||||
|
||||
fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer"
|
||||
fingerprint mfr:"001D", prod:"1902", deviceJoinName: "Z-Wave Dimmer"
|
||||
fingerprint mfr:"001D", prod:"1B03", model:"0334", deviceJoinName: "Leviton Universal Dimmer"
|
||||
fingerprint mfr:"011A", prod:"0102", model:"0201", deviceJoinName: "Enerwave In-Wall Dimmer"
|
||||
fingerprint mfr:"001D", prod:"1001", model:"0334", deviceJoinName: "Leviton 3-Speed Fan Controller"
|
||||
fingerprint mfr:"001D", prod:"0602", model:"0334", deviceJoinName: "Leviton Magnetic Low Voltage Dimmer"
|
||||
fingerprint mfr:"001D", prod:"0401", model:"0334", deviceJoinName: "Leviton 600W Incandescent Dimmer"
|
||||
fingerprint mfr:"0111", prod:"8200", model:"0200", deviceJoinName: "Remotec Technology Plug-In Dimmer"
|
||||
fingerprint mfr:"1104", prod:"001D", model:"0501", deviceJoinName: "Leviton 1000W Incandescant Dimmer"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -25,7 +25,6 @@ metadata {
|
||||
capability "Switch Level"
|
||||
capability "Sensor"
|
||||
capability "Actuator"
|
||||
capability "Light"
|
||||
|
||||
command "reset"
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Light"
|
||||
|
||||
command "reset"
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ metadata {
|
||||
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
|
||||
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
|
||||
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
|
||||
fingerprint mfr: "0063", prod: "4953", model: "3133", deviceJoinName: "GE Smart Motion Sensor"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -81,7 +81,7 @@ def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport
|
||||
def value = "when off"
|
||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
||||
[name: "indicatorStatus", value: value, displayed: false]
|
||||
[name: "indicatorStatus", value: value, display: false]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Z-wave Switch
|
||||
|
||||
Cloud Execution
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -32,8 +32,6 @@ Not to mention after going OFFLINE when the device is plugged back in, it might
|
||||
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.
|
||||
|
||||
@@ -19,18 +19,16 @@ metadata {
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Light"
|
||||
|
||||
fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch"
|
||||
fingerprint deviceId: "0x1001", inClusters: "0x5E,0x20,0x25,0x27,0x72,0x86,0x70,0x85", deviceJoinName: "Z-Wave Switch" // to prevent typing as relay
|
||||
fingerprint deviceId: "0x1003", inClusters: "0x5E,0x25,0x2B,0x2C,0x27,0x75,0x73,0x70,0x86,0x72", deviceJoinName: "Z-Wave Switch"
|
||||
fingerprint mfr:"001D", prod:"1A02", model:"0334", deviceJoinName: "Leviton Appliance Module"
|
||||
fingerprint mfr:"0063", prod:"4F50", model:"3031", deviceJoinName: "GE Plug-in Outdoor Switch"
|
||||
fingerprint mfr:"001D", prod:"3601", model:"0001", deviceJoinName: "Leviton Appliance Module"
|
||||
fingerprint mfr:"001D", prod:"1D04", model:"0334", deviceJoinName: "Leviton Outlet"
|
||||
fingerprint mfr:"001D", prod:"1C02", model:"0334", deviceJoinName: "Leviton Switch"
|
||||
fingerprint mfr:"001D", prod:"0301", model:"0334", deviceJoinName: "Leviton 15A Switch"
|
||||
fingerprint mfr:"001D", prod:"0F01", model:"0334", deviceJoinName: "Leviton 5A Incandescent Switch"
|
||||
fingerprint mfr:"001D", prod:"1603", model:"0334", deviceJoinName: "Leviton 15A Split Duplex Receptacle"
|
||||
fingerprint mfr:"011A", prod:"0101", model:"0102", deviceJoinName: "Enerwave On/Off Switch"
|
||||
fingerprint mfr:"011A", prod:"0101", model:"0603", deviceJoinName: "Enerwave Duplex Receptacle"
|
||||
fingerprint mfr:"001D", prod:"3401", model:"0001", deviceJoinName: "Leviton Switch"
|
||||
fingerprint mfr:"0063", prod:"4F50", model:"3031", deviceJoinName: "GE Plug-in Outdoor Switch"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Z-Wave Switch
|
||||
|
||||
Local Execution on V2 Hubs
|
||||
|
||||
Works with:
|
||||
|
||||
@@ -35,7 +34,6 @@ Not to mention after going OFFLINE when the device is plugged back in, it might
|
||||
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
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch"
|
||||
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
|
||||
@@ -115,7 +114,7 @@ def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport
|
||||
def value = "when off"
|
||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
||||
[name: "indicatorStatus", value: value, displayed: false]
|
||||
[name: "indicatorStatus", value: value, display: false]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||
@@ -175,17 +174,17 @@ def refresh() {
|
||||
}
|
||||
|
||||
void indicatorWhenOn() {
|
||||
sendEvent(name: "indicatorStatus", value: "when on", displayed: false)
|
||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
void indicatorWhenOff() {
|
||||
sendEvent(name: "indicatorStatus", value: "when off", displayed: false)
|
||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
void indicatorNever() {
|
||||
sendEvent(name: "indicatorStatus", value: "never", displayed: false)
|
||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ def authPage() {
|
||||
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
|
||||
section() {
|
||||
paragraph "Tap below to log in to the netatmo and authorize SmartThings access."
|
||||
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}", description:description
|
||||
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -146,24 +146,19 @@ def callback() {
|
||||
|
||||
// log.debug "PARAMS: ${params}"
|
||||
|
||||
try {
|
||||
httpPost(params) { resp ->
|
||||
httpPost(params) { resp ->
|
||||
|
||||
def slurper = new JsonSlurper()
|
||||
def slurper = new JsonSlurper()
|
||||
|
||||
resp.data.each { key, value ->
|
||||
def data = slurper.parseText(key)
|
||||
log.debug "Data: $data"
|
||||
state.refreshToken = data.refresh_token
|
||||
state.authToken = data.access_token
|
||||
//state.accessToken = data.access_token
|
||||
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||
// log.debug "swapped token: $resp.data"
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug "callback: Call failed $e"
|
||||
}
|
||||
resp.data.each { key, value ->
|
||||
def data = slurper.parseText(key)
|
||||
|
||||
state.refreshToken = data.refresh_token
|
||||
state.authToken = data.access_token
|
||||
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||
// log.debug "swapped token: $resp.data"
|
||||
}
|
||||
}
|
||||
|
||||
// Handle success and failure here, and render stuff accordingly
|
||||
if (state.authToken) {
|
||||
@@ -392,18 +387,18 @@ def getDeviceList() {
|
||||
state.deviceDetail = [:]
|
||||
state.deviceState = [:]
|
||||
|
||||
apiGet("/api/getstationsdata") { response ->
|
||||
apiGet("/api/devicelist") { response ->
|
||||
response.data.body.devices.each { value ->
|
||||
def key = value._id
|
||||
deviceList[key] = "${value.station_name}: ${value.module_name}"
|
||||
state.deviceDetail[key] = value
|
||||
state.deviceState[key] = value.dashboard_data
|
||||
value.modules.each { value2 ->
|
||||
def key2 = value2._id
|
||||
deviceList[key2] = "${value.station_name}: ${value2.module_name}"
|
||||
state.deviceDetail[key2] = value2
|
||||
state.deviceState[key2] = value2.dashboard_data
|
||||
}
|
||||
}
|
||||
response.data.body.modules.each { value ->
|
||||
def key = value._id
|
||||
deviceList[key] = "${state.deviceDetail[value.main_device].station_name}: ${value.module_name}"
|
||||
state.deviceDetail[key] = value
|
||||
state.deviceState[key] = value.dashboard_data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +448,6 @@ def listDevices() {
|
||||
}
|
||||
|
||||
def apiGet(String path, Map query, Closure callback) {
|
||||
|
||||
if(now() >= state.tokenExpires) {
|
||||
refreshToken();
|
||||
}
|
||||
@@ -473,16 +467,12 @@ def apiGet(String path, Map query, Closure callback) {
|
||||
} catch (Exception e) {
|
||||
// This is most likely due to an invalid token. Try to refresh it and try again.
|
||||
log.debug "apiGet: Call failed $e"
|
||||
if(refreshToken()) {
|
||||
log.debug "apiGet: Trying again after refreshing token"
|
||||
try {
|
||||
httpGet(params) { response ->
|
||||
callback.call(response)
|
||||
}
|
||||
} catch (Exception f) {
|
||||
log.debug "apiGet: Call failed $f"
|
||||
}
|
||||
}
|
||||
if(refreshToken()) {
|
||||
log.debug "apiGet: Trying again after refreshing token"
|
||||
httpGet(params) { response ->
|
||||
callback.call(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,4 +561,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
|
||||
|
||||
private List getRealHubFirmwareVersions() {
|
||||
return location.hubs*.firmwareVersionString.findAll { it }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/**
|
||||
* Color Coordinator
|
||||
* Version 1.1.1 - 11/9/16
|
||||
* Version 1.1.0 - 11/9/16
|
||||
* By Michael Struck
|
||||
*
|
||||
* 1.0.0 - Initial release
|
||||
* 1.1.0 - Fixed issue where master can be part of slaves. This causes a loop that impacts SmartThings.
|
||||
* 1.1.1 - Fix NPE being thrown for slave/master inputs being empty.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
@@ -34,17 +33,17 @@ preferences {
|
||||
|
||||
def mainPage() {
|
||||
dynamicPage(name: "mainPage", title: "", install: true, uninstall: false) {
|
||||
def masterInList = slaves?.id?.find{it==master?.id}
|
||||
def masterInList = slaves.id.find{it==master.id}
|
||||
if (masterInList) {
|
||||
section ("**WARNING**"){
|
||||
paragraph "You have included the Master Light in the Slave Group. This will cause a loop in execution. Please remove this device from the Slave Group.", image: "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/img/caution.png"
|
||||
}
|
||||
}
|
||||
section("Master Light") {
|
||||
input "master", "capability.colorControl", title: "Colored Light", required: true
|
||||
input "master", "capability.colorControl", title: "Colored Light"
|
||||
}
|
||||
section("Lights that follow the master settings") {
|
||||
input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: true, submitOnChange: true
|
||||
input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: false, submitOnChange: true
|
||||
}
|
||||
section([mobileOnly:true], "Options") {
|
||||
input "randomYes", "bool",title: "When Master Turned On, Randomize Color", defaultValue: false
|
||||
@@ -82,44 +81,40 @@ def init() {
|
||||
}
|
||||
//-----------------------------------
|
||||
def onOffHandler(evt){
|
||||
if (slaves && master) {
|
||||
if (!slaves?.id.find{it==master?.id}){
|
||||
if (master?.currentValue("switch") == "on"){
|
||||
if (randomYes) getRandomColorMaster()
|
||||
else slaves?.on()
|
||||
}
|
||||
else {
|
||||
slaves?.off()
|
||||
}
|
||||
}
|
||||
if (!slaves.id.find{it==master.id}){
|
||||
if (master.currentValue("switch") == "on"){
|
||||
if (randomYes) getRandomColorMaster()
|
||||
else slaves?.on()
|
||||
}
|
||||
else {
|
||||
slaves?.off()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def colorHandler(evt) {
|
||||
if (slaves && master) {
|
||||
if (!slaves?.id?.find{it==master?.id} && master?.currentValue("switch") == "on"){
|
||||
log.debug "Changing Slave units H,S,L"
|
||||
def dimLevel = master?.currentValue("level")
|
||||
def hueLevel = master?.currentValue("hue")
|
||||
def saturationLevel = master.currentValue("saturation")
|
||||
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
|
||||
slaves?.setColor(newValue)
|
||||
try {
|
||||
log.debug "Changing Slave color temp"
|
||||
def tempLevel = master?.currentValue("colorTemperature")
|
||||
slaves?.setColorTemperature(tempLevel)
|
||||
}
|
||||
catch (e){
|
||||
log.debug "Color temp for master --"
|
||||
}
|
||||
}
|
||||
if (!slaves.id.find{it==master.id} && master.currentValue("switch") == "on"){
|
||||
log.debug "Changing Slave units H,S,L"
|
||||
def dimLevel = master.currentValue("level")
|
||||
def hueLevel = master.currentValue("hue")
|
||||
def saturationLevel = master.currentValue("saturation")
|
||||
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
|
||||
slaves?.setColor(newValue)
|
||||
try {
|
||||
log.debug "Changing Slave color temp"
|
||||
def tempLevel = master.currentValue("colorTemperature")
|
||||
slaves?.setColorTemperature(tempLevel)
|
||||
}
|
||||
catch (e){
|
||||
log.debug "Color temp for master --"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getRandomColorMaster(){
|
||||
def hueLevel = Math.floor(Math.random() *1000)
|
||||
def saturationLevel = Math.floor(Math.random() * 100)
|
||||
def dimLevel = master?.currentValue("level")
|
||||
def dimLevel = master.currentValue("level")
|
||||
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
|
||||
log.debug hueLevel
|
||||
log.debug saturationLevel
|
||||
@@ -128,14 +123,12 @@ def getRandomColorMaster(){
|
||||
}
|
||||
|
||||
def tempHandler(evt){
|
||||
if (slaves && master) {
|
||||
if (!slaves?.id?.find{it==master?.id} && master?.currentValue("switch") == "on"){
|
||||
if (evt.value != "--") {
|
||||
log.debug "Changing Slave color temp based on Master change"
|
||||
def tempLevel = master.currentValue("colorTemperature")
|
||||
slaves?.setColorTemperature(tempLevel)
|
||||
}
|
||||
}
|
||||
if (!slaves.id.find{it==master.id} && master.currentValue("switch") == "on"){
|
||||
if (evt.value != "--") {
|
||||
log.debug "Changing Slave color temp based on Master change"
|
||||
def tempLevel = master.currentValue("colorTemperature")
|
||||
slaves?.setColorTemperature(tempLevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +139,7 @@ private def textAppName() {
|
||||
}
|
||||
|
||||
private def textVersion() {
|
||||
def text = "Version 1.1.1 (12/13/2016)"
|
||||
def text = "Version 1.1.0 (11/09/2016)"
|
||||
}
|
||||
|
||||
private def textCopyright() {
|
||||
@@ -173,4 +166,4 @@ private def textHelp() {
|
||||
"This application will allow you to control the settings of multiple colored lights with one control. " +
|
||||
"Simply choose a master control light, and then choose the lights that will follow the settings of the master, "+
|
||||
"including on/off conditions, hue, saturation, level and color temperature. Also includes a random color feature."
|
||||
}
|
||||
}
|
||||
@@ -1,465 +0,0 @@
|
||||
/**
|
||||
* OpenT2T SmartApp Test
|
||||
*
|
||||
* Copyright 2016 OpenT2T
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "OpenT2T SmartApp Test",
|
||||
namespace: "opent2t",
|
||||
author: "OpenT2T",
|
||||
description: "Test app to test end to end SmartThings scenarios via OpenT2T",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
|
||||
|
||||
/** --------------------+---------------+-----------------------+------------------------------------
|
||||
* Device Type | Attribute Name| Commands | Attribute Values
|
||||
* --------------------+---------------+-----------------------+------------------------------------
|
||||
* switches | switch | on, off | on, off
|
||||
* motionSensors | motion | | active, inactive
|
||||
* contactSensors | contact | | open, closed
|
||||
* presenceSensors | presence | | present, 'not present'
|
||||
* temperatureSensors | temperature | | <numeric, F or C according to unit>
|
||||
* accelerationSensors | acceleration | | active, inactive
|
||||
* waterSensors | water | | wet, dry
|
||||
* lightSensors | illuminance | | <numeric, lux>
|
||||
* humiditySensors | humidity | | <numeric, percent>
|
||||
* locks | lock | lock, unlock | locked, unlocked
|
||||
* garageDoors | door | open, close | unknown, closed, open, closing, opening
|
||||
* cameras | image | take | <String>
|
||||
* thermostats | thermostat | setHeatingSetpoint, | temperature, heatingSetpoint, coolingSetpoint,
|
||||
* | | setCoolingSetpoint, | thermostatSetpoint, thermostatMode,
|
||||
* | | off, heat, cool, auto,| thermostatFanMode, thermostatOperatingState
|
||||
* | | emergencyHeat, |
|
||||
* | | setThermostatMode, |
|
||||
* | | fanOn, fanAuto, |
|
||||
* | | fanCirculate, |
|
||||
* | | setThermostatFanMode |
|
||||
* --------------------+---------------+-----------------------+------------------------------------
|
||||
*/
|
||||
|
||||
//Device Inputs
|
||||
preferences {
|
||||
section("Allow OpenT2T to control these things...") {
|
||||
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false
|
||||
input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false
|
||||
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false
|
||||
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false
|
||||
input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Which Thermostat?", multiple: true, required: false
|
||||
input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
|
||||
def getInputs() {
|
||||
def inputList = []
|
||||
inputList += contactSensors ?: []
|
||||
inputList += garageDoors ?: []
|
||||
inputList += locks ?: []
|
||||
inputList += cameras ?: []
|
||||
inputList += motionSensors ?: []
|
||||
inputList += presenceSensors ?: []
|
||||
inputList += switches ?: []
|
||||
inputList += thermostats ?: []
|
||||
inputList += waterSensors ?: []
|
||||
return inputList
|
||||
}
|
||||
|
||||
//API external Endpoints
|
||||
mappings {
|
||||
path("/subscriptionURL/:url") {
|
||||
action:
|
||||
[
|
||||
PUT: "updateEndpointURL"
|
||||
]
|
||||
}
|
||||
path("/connectionId/:connId") {
|
||||
action:
|
||||
[
|
||||
PUT: "updateConnectionId"
|
||||
]
|
||||
}
|
||||
path("/devices") {
|
||||
action:
|
||||
[
|
||||
GET: "getDevices"
|
||||
]
|
||||
}
|
||||
path("/devices/:id") {
|
||||
action:
|
||||
[
|
||||
GET: "getDevice"
|
||||
]
|
||||
}
|
||||
path("/update/:id") {
|
||||
action:
|
||||
[
|
||||
PUT: "updateDevice"
|
||||
]
|
||||
}
|
||||
path("/subscription/:id") {
|
||||
action:
|
||||
[
|
||||
POST : "registerDeviceChange",
|
||||
DELETE: "unregisterDeviceChange"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
unsubscribe()
|
||||
registerSubscriptions()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
state.connectionId = ""
|
||||
state.endpointURL = "https://ifs.windows-int.com/v1/cb/81C7E77B-EABC-488A-B2BF-FEC42F0DABD2/notify"
|
||||
registerSubscriptions()
|
||||
}
|
||||
|
||||
//Subscribe events for all devices
|
||||
def registerSubscriptions() {
|
||||
registerChangeHandler(inputs)
|
||||
}
|
||||
|
||||
//Subscribe to events from a list of devices
|
||||
def registerChangeHandler(myList) {
|
||||
myList.each { myDevice ->
|
||||
def theAtts = myDevice.supportedAttributes
|
||||
theAtts.each { att ->
|
||||
subscribe(myDevice, att.name, eventHandler)
|
||||
log.info "Registering ${myDevice.displayName}.${att.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Endpoints function: Subscribe to events from a specific device
|
||||
def registerDeviceChange() {
|
||||
def myDevice = findDevice(params.id)
|
||||
def theAtts = myDevice.supportedAttributes
|
||||
try {
|
||||
theAtts.each { att ->
|
||||
subscribe(myDevice, att.name, eventHandler)
|
||||
log.info "Registering ${myDevice.displayName}.${att.name}"
|
||||
}
|
||||
return ["succeed"]
|
||||
} catch (e) {
|
||||
httpError(500, "something went wrong: $e")
|
||||
}
|
||||
}
|
||||
|
||||
//Endpoints function: Unsubscribe to events from a specific device
|
||||
def unregisterDeviceChange() {
|
||||
def myDevice = findDevice(params.id)
|
||||
try {
|
||||
unsubscribe(myDevice)
|
||||
log.info "Unregistering ${myDevice.displayName}"
|
||||
return ["succeed"]
|
||||
} catch (e) {
|
||||
httpError(500, "something went wrong: $e")
|
||||
}
|
||||
}
|
||||
|
||||
//When events are triggered, send HTTP post to web socket servers
|
||||
def eventHandler(evt) {
|
||||
def evt_device_id = evt.deviceId
|
||||
def evt_device_value = evt.value
|
||||
def evt_name = evt.name
|
||||
def evt_device = evt.device
|
||||
def evt_deviceType = getDeviceType(evt_device);
|
||||
def params = [
|
||||
uri : "${state.endpointURL}/${state.connectionId}",
|
||||
body: [
|
||||
name : evt_device.displayName,
|
||||
id : evt_device.id,
|
||||
deviceType : evt_deviceType,
|
||||
manufacturer: evt_device.getManufacturerName(),
|
||||
model : evt_device.getModelName(),
|
||||
attributes : deviceAttributeList(evt_device)
|
||||
]
|
||||
]
|
||||
try {
|
||||
log.trace "POST URI: ${params.uri}"
|
||||
log.trace "Payload: ${params.body}"
|
||||
httpPostJson(params) { resp ->
|
||||
resp.headers.each {
|
||||
log.debug "${it.name} : ${it.value}"
|
||||
}
|
||||
log.trace "response status code: ${resp.status}"
|
||||
log.trace "response data: ${resp.data}"
|
||||
}
|
||||
} catch (e) {
|
||||
log.debug "something went wrong: $e"
|
||||
}
|
||||
}
|
||||
|
||||
//Endpoints function: update subcription endpoint url [state.endpoint]
|
||||
void updateEndpointURL() {
|
||||
state.endpointURL = params.url
|
||||
log.info "Updated EndpointURL to ${state.endpointURL}"
|
||||
}
|
||||
|
||||
//Endpoints function: update global variable [state.connectionId]
|
||||
void updateConnectionId() {
|
||||
def connId = params.connId
|
||||
state.connectionId = connId
|
||||
log.info "Updated ConnectionID to ${state.connectionId}"
|
||||
}
|
||||
|
||||
//Endpoints function: return all device data in json format
|
||||
def getDevices() {
|
||||
def deviceData = []
|
||||
inputs?.each {
|
||||
def deviceType = getDeviceType(it)
|
||||
if (deviceType == "thermostat") {
|
||||
deviceData << [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it), locationMode: getLocationModeInfo()]
|
||||
} else {
|
||||
deviceData << [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it)]
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "getDevices, return: ${deviceData}"
|
||||
return deviceData
|
||||
}
|
||||
|
||||
//Endpoints function: get device data
|
||||
def getDevice() {
|
||||
def it = findDevice(params.id)
|
||||
def deviceType = getDeviceType(it)
|
||||
def device
|
||||
if (deviceType == "thermostat") {
|
||||
device = [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it), locationMode: getLocationModeInfo()]
|
||||
} else {
|
||||
device = [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it)]
|
||||
}
|
||||
log.debug "getDevice, return: ${device}"
|
||||
return device
|
||||
}
|
||||
|
||||
//Endpoints function: update device data
|
||||
void updateDevice() {
|
||||
def device = findDevice(params.id)
|
||||
request.JSON.each {
|
||||
def command = it.key
|
||||
def value = it.value
|
||||
if (command) {
|
||||
def commandList = mapDeviceCommands(command, value)
|
||||
command = commandList[0]
|
||||
value = commandList[1]
|
||||
|
||||
if (command == "setAwayMode") {
|
||||
log.info "Setting away mode to ${value}"
|
||||
if (location.modes?.find { it.name == value }) {
|
||||
location.setMode(value)
|
||||
}
|
||||
} else if (command == "thermostatSetpoint") {
|
||||
switch (device.currentThermostatMode) {
|
||||
case "cool":
|
||||
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||
device.setCoolingSetpoint(value)
|
||||
break
|
||||
case "heat":
|
||||
case "emergency heat":
|
||||
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||
device.setHeatingSetpoint(value)
|
||||
break
|
||||
default:
|
||||
httpError(501, "this mode: ${device.currentThermostatMode} does not allow changing thermostat setpoint.")
|
||||
break
|
||||
}
|
||||
} else if (!device) {
|
||||
log.error "updateDevice, Device not found"
|
||||
httpError(404, "Device not found")
|
||||
} else if (!device.hasCommand(command)) {
|
||||
log.error "updateDevice, Device does not have the command"
|
||||
httpError(404, "Device does not have such command")
|
||||
} else {
|
||||
if (command == "setColor") {
|
||||
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||
device."$command"(hex: value)
|
||||
} else if (value.isNumber()) {
|
||||
def intValue = value as Integer
|
||||
log.info "Update: ${device.displayName}, [${command}, ${intValue}(int)]"
|
||||
device."$command"(intValue)
|
||||
} else if (value) {
|
||||
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||
device."$command"(value)
|
||||
} else {
|
||||
log.info "Update: ${device.displayName}, [${command}]"
|
||||
device."$command"()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*** Private Functions ***/
|
||||
|
||||
//Return current location mode info
|
||||
private getLocationModeInfo() {
|
||||
return [mode: location.mode, supported: location.modes.name]
|
||||
}
|
||||
|
||||
//Map each device to a type given it's capabilities
|
||||
private getDeviceType(device) {
|
||||
def deviceType
|
||||
def caps = device.capabilities
|
||||
log.debug "capabilities: [${device}, ${caps}]"
|
||||
log.debug "supported commands: [${device}, ${device.supportedCommands}]"
|
||||
caps.each {
|
||||
switch (it.name.toLowerCase()) {
|
||||
case "switch":
|
||||
deviceType = "switch"
|
||||
if (caps.any { it.name.toLowerCase() == "power meter" }) {
|
||||
return deviceType
|
||||
}
|
||||
if (caps.any { it.name.toLowerCase() == "switch level" }) {
|
||||
deviceType = "light"
|
||||
return deviceType
|
||||
}
|
||||
break
|
||||
case "contact sensor":
|
||||
deviceType = "contactSensor"
|
||||
return deviceType
|
||||
case "garageDoorControl":
|
||||
deviceType = "garageDoor"
|
||||
return deviceType
|
||||
case "lock":
|
||||
deviceType = "lock"
|
||||
return deviceType
|
||||
case "video camera":
|
||||
deviceType = "camera"
|
||||
return deviceType
|
||||
case "motion sensor":
|
||||
deviceType = "motionSensor"
|
||||
return deviceType
|
||||
case "presence sensor":
|
||||
deviceType = "presenceSensor"
|
||||
return deviceType
|
||||
case "thermostat":
|
||||
deviceType = "thermostat"
|
||||
return deviceType
|
||||
case "water sensor":
|
||||
deviceType = "waterSensor"
|
||||
return deviceType
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return deviceType
|
||||
}
|
||||
|
||||
//Return a specific device give the device ID.
|
||||
private findDevice(deviceId) {
|
||||
return inputs?.find { it.id == deviceId }
|
||||
}
|
||||
|
||||
//Return a list of device attributes
|
||||
private deviceAttributeList(device) {
|
||||
device.supportedAttributes.collectEntries { attribute ->
|
||||
try {
|
||||
[(attribute.name): device.currentValue(attribute.name)]
|
||||
} catch (e) {
|
||||
[(attribute.name): null]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Map device command and value.
|
||||
//input command and value are from UWP,
|
||||
//returns resultCommand and resultValue that corresponds with function and value in SmartApps
|
||||
private mapDeviceCommands(command, value) {
|
||||
log.debug "mapDeviceCommands: [${command}, ${value}]"
|
||||
def resultCommand = command
|
||||
def resultValue = value
|
||||
switch (command) {
|
||||
case "switch":
|
||||
if (value == 1 || value == "1" || value == "on") {
|
||||
resultCommand = "on"
|
||||
resultValue = ""
|
||||
} else if (value == 0 || value == "0" || value == "off") {
|
||||
resultCommand = "off"
|
||||
resultValue = ""
|
||||
}
|
||||
break
|
||||
// light attributes
|
||||
case "level":
|
||||
resultCommand = "setLevel"
|
||||
resultValue = value
|
||||
break
|
||||
case "hue":
|
||||
resultCommand = "setHue"
|
||||
resultValue = value
|
||||
break
|
||||
case "saturation":
|
||||
resultCommand = "setSaturation"
|
||||
resultValue = value
|
||||
break
|
||||
case "ct":
|
||||
resultCommand = "setColorTemperature"
|
||||
resultValue = value
|
||||
break
|
||||
case "color":
|
||||
resultCommand = "setColor"
|
||||
resultValue = value
|
||||
// thermostat attributes
|
||||
case "hvacMode":
|
||||
resultCommand = "setThermostatMode"
|
||||
resultValue = value
|
||||
break
|
||||
case "fanMode":
|
||||
resultCommand = "setThermostatFanMode"
|
||||
resultValue = value
|
||||
break
|
||||
case "awayMode":
|
||||
resultCommand = "setAwayMode"
|
||||
resultValue = value
|
||||
break
|
||||
case "coolingSetpoint":
|
||||
resultCommand = "setCoolingSetpoint"
|
||||
resultValue = value
|
||||
break
|
||||
case "heatingSetpoint":
|
||||
resultCommand = "setHeatingSetpoint"
|
||||
resultValue = value
|
||||
break
|
||||
case "thermostatSetpoint":
|
||||
resultCommand = "thermostatSetpoint"
|
||||
resultValue = value
|
||||
break
|
||||
// lock attributes
|
||||
case "locked":
|
||||
if (value == 1 || value == "1" || value == "lock") {
|
||||
resultCommand = "lock"
|
||||
resultValue = ""
|
||||
} else if (value == 0 || value == "0" || value == "unlock") {
|
||||
resultCommand = "unlock"
|
||||
resultValue = ""
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return [resultCommand, resultValue]
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ def authPage(){
|
||||
atomicState.accessToken = state.accessToken
|
||||
}
|
||||
|
||||
def redirectUrl = oauthInitUrl()
|
||||
def redirectUrl = oauthInitUrl()
|
||||
def uninstallAllowed = false
|
||||
def oauthTokenProvided = false
|
||||
if(atomicState.authToken){
|
||||
@@ -78,9 +78,9 @@ def authPage(){
|
||||
}
|
||||
}else{
|
||||
return dynamicPage(name: "auth", title: "Step 1 of 2 - Completed", nextPage:"deviceList", uninstall:uninstallAllowed) {
|
||||
section(){
|
||||
section(){
|
||||
paragraph "You are logged in to myplantlink.com, tap next to continue", image: iconUrl
|
||||
href(url:redirectUrl, title:"Or", description:"tap to switch accounts")
|
||||
href(url:redirectUrl, title:"Or", description:"tap to switch accounts")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,44 +137,36 @@ def dock_sensor(device_serial, expected_plant_name) {
|
||||
contentType: "application/json",
|
||||
]
|
||||
log.debug "Creating new plant on myplantlink.com - ${expected_plant_name}"
|
||||
try {
|
||||
httpPost(docking_params) { docking_response ->
|
||||
if (parse_api_response(docking_response, "Docking a link")) {
|
||||
if (docking_response.data.plants.size() == 0) {
|
||||
log.debug "creating plant for - ${expected_plant_name}"
|
||||
plant_post_body_map["name"] = expected_plant_name
|
||||
plant_post_body_map['links_key'] = [docking_response.data.key]
|
||||
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
|
||||
plant_post_params["body"] = plant_post_body_json_builder.toString()
|
||||
try {
|
||||
httpPost(plant_post_params) { plant_post_response ->
|
||||
if(parse_api_response(plant_post_response, 'creating plant')){
|
||||
def attached_map = atomicState.attached_sensors
|
||||
attached_map[device_serial] = plant_post_response.data
|
||||
atomicState.attached_sensors = attached_map
|
||||
}
|
||||
}
|
||||
} catch (Exception f) {
|
||||
log.debug "call failed $f"
|
||||
httpPost(docking_params) { docking_response ->
|
||||
if (parse_api_response(docking_response, "Docking a link")) {
|
||||
if (docking_response.data.plants.size() == 0) {
|
||||
log.debug "creating plant for - ${expected_plant_name}"
|
||||
plant_post_body_map["name"] = expected_plant_name
|
||||
plant_post_body_map['links_key'] = [docking_response.data.key]
|
||||
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
|
||||
plant_post_params["body"] = plant_post_body_json_builder.toString()
|
||||
httpPost(plant_post_params) { plant_post_response ->
|
||||
if(parse_api_response(plant_post_response, 'creating plant')){
|
||||
def attached_map = atomicState.attached_sensors
|
||||
attached_map[device_serial] = plant_post_response.data
|
||||
atomicState.attached_sensors = attached_map
|
||||
}
|
||||
} else {
|
||||
def plant = docking_response.data.plants[0]
|
||||
def attached_map = atomicState.attached_sensors
|
||||
attached_map[device_serial] = plant
|
||||
atomicState.attached_sensors = attached_map
|
||||
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
|
||||
}
|
||||
} else {
|
||||
def plant = docking_response.data.plants[0]
|
||||
def attached_map = atomicState.attached_sensors
|
||||
attached_map[device_serial] = plant
|
||||
atomicState.attached_sensors = attached_map
|
||||
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug "call failed $e"
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
|
||||
def plant_put_params = [
|
||||
uri : appSettings.https_plantLinkServer,
|
||||
uri : appSettings.https_plantLinkServer,
|
||||
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||
contentType : "application/json"
|
||||
]
|
||||
@@ -182,16 +174,12 @@ def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
|
||||
log.debug "updating plant for - ${expected_plant_name}"
|
||||
plant_put_params["path"] = "/api/v1/plants/${plant.key}"
|
||||
def plant_put_body_map = [
|
||||
name: expected_plant_name
|
||||
name: expected_plant_name
|
||||
]
|
||||
def plant_put_body_json_builder = new JsonBuilder(plant_put_body_map)
|
||||
plant_put_params["body"] = plant_put_body_json_builder.toString()
|
||||
try {
|
||||
httpPut(plant_put_params) { plant_put_response ->
|
||||
parse_api_response(plant_put_response, 'updating plant name')
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug "call failed $e"
|
||||
httpPut(plant_put_params) { plant_put_response ->
|
||||
parse_api_response(plant_put_response, 'updating plant name')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,29 +198,25 @@ def moistureHandler(event){
|
||||
contentType: "application/json",
|
||||
body: event.value
|
||||
]
|
||||
try {
|
||||
httpPost(measurement_post_params) { measurement_post_response ->
|
||||
if (parse_api_response(measurement_post_response, 'creating moisture measurement') &&
|
||||
measurement_post_response.data.size() >0){
|
||||
def measurement = measurement_post_response.data[0]
|
||||
def plant = measurement.plant
|
||||
log.debug plant
|
||||
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
|
||||
plantlinksensors.each{ sensor_device ->
|
||||
if (sensor_device.id == event.deviceId){
|
||||
sensor_device.setStatusIcon(plant.status)
|
||||
if (plant.last_measurements && plant.last_measurements[0].moisture){
|
||||
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
|
||||
}
|
||||
if (plant.last_measurements && plant.last_measurements[0].battery){
|
||||
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
|
||||
}
|
||||
httpPost(measurement_post_params) { measurement_post_response ->
|
||||
if (parse_api_response(measurement_post_response, 'creating moisture measurement') &&
|
||||
measurement_post_response.data.size() >0){
|
||||
def measurement = measurement_post_response.data[0]
|
||||
def plant = measurement.plant
|
||||
log.debug plant
|
||||
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
|
||||
plantlinksensors.each{ sensor_device ->
|
||||
if (sensor_device.id == event.deviceId){
|
||||
sensor_device.setStatusIcon(plant.status)
|
||||
if (plant.last_measurements && plant.last_measurements[0].moisture){
|
||||
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
|
||||
}
|
||||
if (plant.last_measurements && plant.last_measurements[0].battery){
|
||||
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug "call failed $e"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,12 +235,8 @@ def batteryHandler(event){
|
||||
contentType: "application/json",
|
||||
body: event.value
|
||||
]
|
||||
try {
|
||||
httpPost(measurement_post_params) { measurement_post_response ->
|
||||
parse_api_response(measurement_post_response, 'creating battery measurement')
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug "call failed $e"
|
||||
httpPost(measurement_post_params) { measurement_post_response ->
|
||||
parse_api_response(measurement_post_response, 'creating battery measurement')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,7 +248,7 @@ def getDeviceSerialFromEvent(event){
|
||||
}
|
||||
|
||||
def oauthInitUrl(){
|
||||
atomicState.oauthInitState = UUID.randomUUID().toString()
|
||||
atomicState.oauthInitState = UUID.randomUUID().toString()
|
||||
def oauthParams = [
|
||||
response_type: "code",
|
||||
client_id: appSettings.client_id,
|
||||
@@ -295,12 +275,8 @@ def swapToken(){
|
||||
]
|
||||
|
||||
def jsonMap
|
||||
try {
|
||||
httpPost(postParams) { resp ->
|
||||
jsonMap = resp.data
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug "call failed $e"
|
||||
httpPost(postParams) { resp ->
|
||||
jsonMap = resp.data
|
||||
}
|
||||
|
||||
atomicState.refreshToken = jsonMap.refresh_token
|
||||
@@ -311,33 +287,33 @@ def swapToken(){
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
.container {
|
||||
padding:25px;
|
||||
}
|
||||
.flex1 {
|
||||
width:33%;
|
||||
float:left;
|
||||
text-align: center;
|
||||
}
|
||||
p {
|
||||
font-size: 2em;
|
||||
font-family: Verdana, Geneva, sans-serif;
|
||||
text-align: center;
|
||||
color: #777;
|
||||
}
|
||||
.container {
|
||||
padding:25px;
|
||||
}
|
||||
.flex1 {
|
||||
width:33%;
|
||||
float:left;
|
||||
text-align: center;
|
||||
}
|
||||
p {
|
||||
font-size: 2em;
|
||||
font-family: Verdana, Geneva, sans-serif;
|
||||
text-align: center;
|
||||
color: #777;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="flex1"><img src="https://dashboard.myplantlink.com/images/PLlogo.png" alt="PlantLink" height="75"/></div>
|
||||
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected to" height="25" style="padding-top:25px;" /></div>
|
||||
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings" height="75"/></div>
|
||||
<br clear="all">
|
||||
<div class="container">
|
||||
<div class="flex1"><img src="https://dashboard.myplantlink.com/images/PLlogo.png" alt="PlantLink" height="75"/></div>
|
||||
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected to" height="25" style="padding-top:25px;" /></div>
|
||||
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings" height="75"/></div>
|
||||
<br clear="all">
|
||||
</div>
|
||||
<div class="container">
|
||||
<p>Your PlantLink Account is now connected to SmartThings!</p>
|
||||
<p style="color:green;">Click <strong>Done</strong> at the top right to finish setup.</p>
|
||||
</div>
|
||||
<p>Your PlantLink Account is now connected to SmartThings!</p>
|
||||
<p style="color:green;">Click <strong>Done</strong> at the top right to finish setup.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
author: "SmartThings",
|
||||
description: "Control your Bose SoundTouch speakers",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon.png",
|
||||
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x.png",
|
||||
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x-1.png",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
singleInstance: true
|
||||
)
|
||||
|
||||
@@ -104,7 +104,7 @@ def deviceDiscovery()
|
||||
|
||||
return dynamicPage(name:"deviceDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||
section("Please wait while we discover your ${getDeviceName()}. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selecteddevice", "enum", required:false, title:"Select ${getDeviceName()} (${numFound} found)", multiple:true, options:devices, submitOnChange: true
|
||||
input "selecteddevice", "enum", required:false, title:"Select ${getDeviceName()} (${numFound} found)", multiple:true, options:devices
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,8 +196,6 @@ def addDevice(){
|
||||
d = addChildDevice(getNameSpace(), getDeviceName(), dni, newDevice?.value.hub, [label:"${deviceName}"])
|
||||
d.boseSetDeviceID(newDevice.value.deviceID)
|
||||
log.trace "Created ${d.displayName} with id $dni"
|
||||
// sync DTH with device, done here as it currently don't work from the DTH's installed() method
|
||||
d.refresh()
|
||||
} else {
|
||||
log.trace "${d.displayName} with id $dni already exists"
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
* JLH - 02-15-2014 - Fuller use of ecobee API
|
||||
* 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines
|
||||
*/
|
||||
|
||||
include 'localization'
|
||||
|
||||
definition(
|
||||
name: "Ecobee (Connect)",
|
||||
namespace: "smartthings",
|
||||
@@ -89,7 +86,7 @@ def authPage() {
|
||||
if (numFound > 0) {
|
||||
section("") {
|
||||
paragraph "Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings."
|
||||
input(name: "ecobeesensors", title: "Select Ecobee Sensors ({{numFound}} found)", messageArgs: [numFound: numFound], type: "enum", required:false, description: "Tap to choose", multiple:true, options:options)
|
||||
input(name: "ecobeesensors", title:"Select Ecobee Sensors (${numFound} found)", type: "enum", required:false, description: "Tap to choose", multiple:true, options:options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
'''Connect your Ecobee thermostat to SmartThings.'''.ar=قم بتوصيل ثرموستات Ecobee بـ SmartThings.
|
||||
'''You are connected.'''.ar=أنت متصل.
|
||||
'''Click to enter Ecobee Credentials'''.ar=النقر لإدخال بيانات اعتماد Ecobee
|
||||
'''Login'''.ar=تسجيل الدخول
|
||||
'''Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button.'''.ar=انقر أدناه لتسجيل الدخول إلى خدمة ecobee والمصادقة على الوصول إلى SmartThings. تأكد من التمرير للأسفل على الصفحة ٢ والضغط على زر ”السماح“.
|
||||
'''Select Your Thermostats'''.ar=تحديد الثرموستات
|
||||
'''Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings.'''.ar=انقر أدناه لرؤية قائمة أجهزة ثرموستات ecobee المتوفرة في حساب ecobee، وحدد الأجهزة التي ترغب في توصيلها بـ SmartThings.
|
||||
'''Tap to choose'''.ar=النقر لاختيار
|
||||
'''Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings.'''.ar=انقر أدناه لرؤية قائمة مستشعرات ecobee المتوفرة في حساب ecobee، وحدد الأجهزة التي ترغب في توصيلها بـ SmartThings.
|
||||
'''Select Ecobee Sensors ({{numFound}} found)'''.ar=تحديد مستشعرات Ecobee ({{numFound}} found)
|
||||
'''Your ecobee Account is now connected to SmartThings!'''.ar=حساب ecobee متصل الآن بـ SmartThings!
|
||||
'''Click 'Done' to finish setup.'''.ar=انقر فوق ”تم“ لإنهاء الإعداد.
|
||||
'''The connection could not be established!'''.ar=يتعذر إنشاء الاتصال!
|
||||
'''Click 'Done' to return to the menu.'''.ar=انقر فوق ”تم“ للعودة إلى القائمة.
|
||||
'''{{deviceName}} is connected to SmartThings'''.ar={{deviceName}} متصل بـ SmartThings
|
||||
'''{{deviceName}} is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials.'''.ar=تم قطع اتصال {{deviceName}} بـ SmartThings، لأن بيانات اعتماد الوصول قد تغيرت أو فُقدت. يُرجى الانتقال إلى التطبيق الذكي Ecobee (Connect) وإعادة إدخال بيانات اعتماد تسجيل الدخول إلى حسابك.
|
||||
'''Your Ecobee thermostat'''.ar=ثرموستات Ecobee
|
||||
@@ -1,17 +0,0 @@
|
||||
'''Connect your Ecobee thermostat to SmartThings.'''.th=เชื่อมต่อตัวควบคุมอุณหภูมิ Ecobee ของคุณเข้ากับ SmartThings
|
||||
'''You are connected.'''.th=คุณได้เชื่อมต่อแล้ว
|
||||
'''Click to enter Ecobee Credentials'''.th=คลิกเพื่อใส่ ข้อมูลยืนยันตัวตน Ecobee
|
||||
'''Login'''.th=เข้าสู่ระบบ
|
||||
'''Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button.'''.th=แตะด้านล่างเพื่อเข้าสู่บริการ Ecobee และอนุญาตการเข้าถึงของ SmartThings ดูให้แน่ใจว่าได้เลื่อนลงมาที่หน้า 2 แล้วกดปุ่ม 'อนุญาต'
|
||||
'''Select Your Thermostats'''.th=เลือกตัวควบคุมอุณหภูมิของคุณ
|
||||
'''Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings.'''.th=แตะที่ด้านล่างเพื่อดูรายการตัวควบคุมอุณหภูมิ Ecobee ที่มีอยู่ในบัญชีผู้ใช้ Ecobee ของคุณ และเลือกตัวควบคุมอุณหภูมิที่คุณต้องการจะเชื่อมต่อกับ SmartThings
|
||||
'''Tap to choose'''.th=แตะเพื่อเลือก
|
||||
'''Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings.'''.th=แตะที่ด้านล่างเพื่อดูรายการเซ็นเซอร์ Ecobee ที่มีอยู่ในบัญชีผู้ใช้ Ecobee ของคุณ และเลือกเซ็นเซอร์ที่คุณต้องการจะเชื่อมต่อกับ SmartThings
|
||||
'''Select Ecobee Sensors ({{numFound} found)'''.th=เลือกเซ็นเซอร์ Ecobee ({{numFound}} found)
|
||||
'''Your ecobee Account is now connected to SmartThings!'''.th=ตอนนี้บัญชีผู้ใช้ Ecobee ของคุณเชื่อมต่อกับ SmartThings แล้ว
|
||||
'''Click 'Done' to finish setup.'''.th=คลิก 'เสร็จสิ้น' เพื่อทำการตั้งค่าให้เสร็จสิ้น
|
||||
'''The connection could not be established!'''.th=ไม่สามารถสร้างการเชื่อมต่อได้!
|
||||
'''Click 'Done' to return to the menu.'''.th=คลิก 'เสร็จสิ้น' เพื่อกลับไปยังเมนู
|
||||
'''{{deviceName}} is connected to SmartThings'''.th={{deviceName}} เชื่อมต่อกับ SmartThings แล้ว
|
||||
'''{{deviceName}} is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials.'''.th={{deviceName}} ถูกตัดการเชื่อมต่อจาก SmartThings เนื่องจากข้อมูลการเข้าถึงถูกเปลี่ยนแปลงหรือหายไป กรุณาไปที่ Ecobee (การเชื่อมต่อ) SmartApp และใส่ข้อมูลยืนยันตัวตนการเข้าสู่บัญชีผู้ใช้ของคุณอีกครั้ง
|
||||
'''Your Ecobee thermostat'''.th=ตัวควบคุมอุณหภูมิ Ecobee ของคุณ
|
||||
@@ -1,17 +0,0 @@
|
||||
'''Connect your Ecobee thermostat to SmartThings.'''.tr=Ecobee termostatınızı SmartThings'e bağlayın.
|
||||
'''You are connected.'''.tr=Bağlantı kurdunuz.
|
||||
'''Click to enter Ecobee Credentials'''.tr=Ecobee Kimlik Bilgilerinizi girmek için tıklayın
|
||||
'''Login'''.tr=Oturum aç
|
||||
'''Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button.'''.tr=Ecobee servisinde oturum açmak ve SmartThings erişimine izin vermek için aşağıya dokunun. Ekranı 2. sayfaya kaydırdığınızdan emin olun ve 'İzin Ver' tuşuna basın.
|
||||
'''Select Your Thermostats'''.tr=Termostatlarınızı Seçin
|
||||
'''Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings.'''.tr=Ecobee hesabınızda mevcut olan ecobee termostatlarının listesini görüntülemek için aşağıya dokunun ve SmartThings'e bağlamak istediklerinizi seçin.
|
||||
'''Tap to choose'''.tr=Seçmek için dokunun
|
||||
'''Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings.'''.tr=Ecobee hesabınızda mevcut olan ecobee sensörlerinin listesini görüntülemek için aşağıya dokunun ve SmartThings'e bağlamak istediklerinizi seçin.
|
||||
'''Select Ecobee Sensors ({{numFound}} found)'''.tr=Ecobee Sensörlerini seçin ({{numFound}} bulundu)
|
||||
'''Your ecobee Account is now connected to SmartThings!'''.tr=Ecobee Hesabınız artık SmartThings'e bağlandı!
|
||||
'''Click 'Done' to finish setup.'''.tr=Kurulumu bitirmek için 'Bitti' öğesine tıklayın.
|
||||
'''The connection could not be established!'''.tr=Bağlantı kurulamadı!
|
||||
'''Click 'Done' to return to the menu.'''.tr=Menüye dönmek için 'Bitti' öğesine tıklayın.
|
||||
'''{{deviceName}} is connected to SmartThings'''.tr={{cihazİsmi}} SmartThings'e bağlandı
|
||||
'''{{deviceName}} is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials.'''.tr=Ecobee termostatınız
|
||||
'''Your Ecobee thermostat'''.tr=Ecobee termostatınız
|
||||
@@ -17,14 +17,14 @@
|
||||
*/
|
||||
|
||||
definition(
|
||||
name: "Hue (Connect)",
|
||||
namespace: "smartthings",
|
||||
author: "SmartThings",
|
||||
description: "Allows you to connect your Philips Hue lights with SmartThings and control them from your Things area or Dashboard in the SmartThings Mobile app. Adjust colors by going to the Thing detail screen for your Hue lights (tap the gear on Hue tiles).\n\nPlease update your Hue Bridge first, outside of the SmartThings app, using the Philips Hue app.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
|
||||
singleInstance: true
|
||||
name: "Hue (Connect)",
|
||||
namespace: "smartthings",
|
||||
author: "SmartThings",
|
||||
description: "Allows you to connect your Philips Hue lights with SmartThings and control them from your Things area or Dashboard in the SmartThings Mobile app. Adjust colors by going to the Thing detail screen for your Hue lights (tap the gear on Hue tiles).\n\nPlease update your Hue Bridge first, outside of the SmartThings app, using the Philips Hue app.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
|
||||
singleInstance: true
|
||||
)
|
||||
|
||||
preferences {
|
||||
@@ -85,8 +85,7 @@ def bridgeDiscovery(params=[:])
|
||||
}
|
||||
|
||||
return dynamicPage(name:"bridgeDiscovery", title:"Discovery Started!", nextPage:"bridgeBtnPush", refreshInterval:refreshInterval, uninstall: true) {
|
||||
section("Please wait while we discover your Hue Bridge. Kindly note that you must first configure your Hue Bridge and Lights using the Philips Hue application. " +
|
||||
"Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
section("Please wait while we discover your Hue Bridge. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options, submitOnChange: true
|
||||
}
|
||||
}
|
||||
@@ -173,34 +172,18 @@ def bulbDiscovery() {
|
||||
if (existingLightsDescription.isEmpty()) {
|
||||
existingLightsDescription += it.value
|
||||
} else {
|
||||
existingLightsDescription += ", ${it.value}"
|
||||
existingLightsDescription += ", ${it.value}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bulbRefreshCount > 200 && numFound == 0) {
|
||||
// Time out after 10 minutes
|
||||
state.inBulbDiscovery = false
|
||||
bulbRefreshCount = 0
|
||||
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Failed!", nextPage:"", refreshInterval:0, install:true, uninstall: true) {
|
||||
section("Failed to discover any lights, please try again later. Click Done to exit.") {
|
||||
//input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
|
||||
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
|
||||
}
|
||||
section {
|
||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||
}
|
||||
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
|
||||
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
|
||||
}
|
||||
|
||||
} else {
|
||||
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
|
||||
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
|
||||
}
|
||||
section {
|
||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||
}
|
||||
section {
|
||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,32 +200,32 @@ private sendDeveloperReq() {
|
||||
def token = app.id
|
||||
def host = getBridgeIP()
|
||||
sendHubCommand(new physicalgraph.device.HubAction([
|
||||
method: "POST",
|
||||
path: "/api",
|
||||
headers: [
|
||||
HOST: host
|
||||
],
|
||||
body: [devicetype: "$token-0"]], "${selectedHue}", [callback: "usernameHandler"]))
|
||||
method: "POST",
|
||||
path: "/api",
|
||||
headers: [
|
||||
HOST: host
|
||||
],
|
||||
body: [devicetype: "$token-0"]], "${selectedHue}", [callback: "usernameHandler"]))
|
||||
}
|
||||
|
||||
private discoverHueBulbs() {
|
||||
def host = getBridgeIP()
|
||||
sendHubCommand(new physicalgraph.device.HubAction([
|
||||
method: "GET",
|
||||
path: "/api/${state.username}/lights",
|
||||
headers: [
|
||||
HOST: host
|
||||
]], "${selectedHue}", [callback: "lightsHandler"]))
|
||||
method: "GET",
|
||||
path: "/api/${state.username}/lights",
|
||||
headers: [
|
||||
HOST: host
|
||||
]], "${selectedHue}", [callback: "lightsHandler"]))
|
||||
}
|
||||
|
||||
private verifyHueBridge(String deviceNetworkId, String host) {
|
||||
log.trace "Verify Hue Bridge $deviceNetworkId"
|
||||
sendHubCommand(new physicalgraph.device.HubAction([
|
||||
method: "GET",
|
||||
path: "/description.xml",
|
||||
headers: [
|
||||
HOST: host
|
||||
]], deviceNetworkId, [callback: "bridgeDescriptionHandler"]))
|
||||
method: "GET",
|
||||
path: "/description.xml",
|
||||
headers: [
|
||||
HOST: host
|
||||
]], deviceNetworkId, [callback: "bridgeDescriptionHandler"]))
|
||||
}
|
||||
|
||||
private verifyHueBridges() {
|
||||
@@ -400,7 +383,7 @@ def addBulbs() {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
} else {
|
||||
//backwards compatable
|
||||
//backwards compatable
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
||||
d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
d?.completedSetup = true
|
||||
@@ -424,7 +407,7 @@ def addBridge() {
|
||||
if(vbridge) {
|
||||
def d = getChildDevice(selectedHue)
|
||||
if(!d) {
|
||||
// compatibility with old devices
|
||||
// compatibility with old devices
|
||||
def newbridge = true
|
||||
childDevices.each {
|
||||
if (it.getDeviceDataByName("mac")) {
|
||||
@@ -610,7 +593,7 @@ def locationHandler(evt) {
|
||||
log.trace "Location: $description"
|
||||
|
||||
def hub = evt?.hubId
|
||||
def parsedEvent = parseLanMessage(description)
|
||||
def parsedEvent = parseLanMessage(description)
|
||||
parsedEvent << ["hub":hub]
|
||||
|
||||
if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:basic:1")) {
|
||||
@@ -836,7 +819,8 @@ def parse(childDevice, description) {
|
||||
try {
|
||||
body = new groovy.json.JsonSlurper().parseText(bodyString)
|
||||
} catch (all) {
|
||||
log.warn "Parsing Body failed"
|
||||
log.warn "Parsing Body failed - trying again..."
|
||||
poll()
|
||||
}
|
||||
if (body instanceof java.util.Map) {
|
||||
// get (poll) reponse
|
||||
@@ -860,7 +844,7 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
|
||||
|
||||
def events = [:]
|
||||
// For now, only care about changing color temperature if requested by user
|
||||
if (ct != null && ct != 0 && (colormode == "ct" || (xy == null && hue == null && sat == null))) {
|
||||
if (ct != null && (colormode == "ct" || (xy == null && hue == null && sat == null))) {
|
||||
// for some reason setting Hue to their specified minimum off 153 yields 154, dealt with below
|
||||
// 153 (6500K) to 500 (2000K)
|
||||
def temp = (ct == 154) ? 6500 : Math.round(1000000 / ct)
|
||||
@@ -1152,7 +1136,7 @@ def setColorTemperature(childDevice, huesettings) {
|
||||
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
|
||||
createSwitchEvent(childDevice, "on")
|
||||
put("lights/$id/state", [ct: ct, on: true])
|
||||
return "Setting color temperature to $ct"
|
||||
return "Setting color temperature to $percent"
|
||||
}
|
||||
|
||||
def setColor(childDevice, huesettings) {
|
||||
@@ -1227,7 +1211,7 @@ private poll() {
|
||||
def uri = "/api/${state.username}/lights/"
|
||||
log.debug "GET: $host$uri"
|
||||
sendHubCommand(new physicalgraph.device.HubAction("GET ${uri} HTTP/1.1\r\n" +
|
||||
"HOST: ${host}\r\n\r\n", physicalgraph.device.Protocol.LAN, selectedHue))
|
||||
"HOST: ${host}\r\n\r\n", physicalgraph.device.Protocol.LAN, selectedHue))
|
||||
}
|
||||
|
||||
private isOnline(id) {
|
||||
@@ -1244,10 +1228,10 @@ private put(path, body) {
|
||||
log.debug "BODY: ${bodyJSON}"
|
||||
|
||||
sendHubCommand(new physicalgraph.device.HubAction("PUT $uri HTTP/1.1\r\n" +
|
||||
"HOST: ${host}\r\n" +
|
||||
"Content-Length: ${length}\r\n" +
|
||||
"\r\n" +
|
||||
"${bodyJSON}", physicalgraph.device.Protocol.LAN, "${selectedHue}"))
|
||||
"HOST: ${host}\r\n" +
|
||||
"Content-Length: ${length}\r\n" +
|
||||
"\r\n" +
|
||||
"${bodyJSON}", physicalgraph.device.Protocol.LAN, "${selectedHue}"))
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1268,7 +1252,7 @@ private getBridgeIP() {
|
||||
if (d) {
|
||||
if (d.getDeviceDataByName("networkAddress"))
|
||||
host = d.getDeviceDataByName("networkAddress")
|
||||
else
|
||||
else
|
||||
host = d.latestState('networkAddress').stringValue
|
||||
}
|
||||
if (host == null || host == "") {
|
||||
|
||||
@@ -289,12 +289,12 @@ def initializeLife360Connection() {
|
||||
state.life360AccessToken = result.data.access_token
|
||||
return true;
|
||||
}
|
||||
log.info "Life360 initializeLife360Connection, response=${result.data}"
|
||||
log.debug "Response=${result.data}"
|
||||
return false;
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
log.error "Life360 initializeLife360Connection, error: $e"
|
||||
log.debug e
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -656,7 +656,7 @@ def generateInitialEvent (member, childDevice) {
|
||||
|
||||
try { // we are going to just ignore any errors
|
||||
|
||||
log.info "Life360 generateInitialEvent($member, $childDevice)"
|
||||
log.debug "Generate Initial Event for New Device for Member = ${member.id}"
|
||||
|
||||
def place = state.places.find{it.id==settings.place}
|
||||
|
||||
@@ -677,8 +677,6 @@ def generateInitialEvent (member, childDevice) {
|
||||
// log.debug "Distance Away = ${distanceAway}"
|
||||
|
||||
boolean isPresent = (distanceAway <= placeRadius)
|
||||
|
||||
log.info "Life360 generateInitialEvent, member: ($memberLatitude, $memberLongitude), place: ($placeLatitude, $placeLongitude), radius: $placeRadius, dist: $distanceAway, present: $isPresent"
|
||||
|
||||
// log.debug "External Id=${app.id}:${member.id}"
|
||||
|
||||
@@ -720,7 +718,7 @@ def haversine(lat1, lon1, lat2, lon2) {
|
||||
|
||||
def placeEventHandler() {
|
||||
|
||||
log.info "Life360 placeEventHandler: params=$params, settings.place=$settings.place"
|
||||
log.debug "In placeEventHandler method."
|
||||
|
||||
// the POST to this end-point will look like:
|
||||
// POST http://test.com/webhook?circleId=XXXX&placeId=XXXX&userId=XXXX&direction=arrive
|
||||
@@ -731,6 +729,8 @@ def placeEventHandler() {
|
||||
def direction = params?.direction
|
||||
def timestamp = params?.timestamp
|
||||
|
||||
log.debug "Life360 Event: Circle: ${circleId}, Place: ${placeId}, User: ${userId}, Direction: ${direction}"
|
||||
|
||||
if (placeId == settings.place) {
|
||||
|
||||
def presenceState = (direction=="in")
|
||||
@@ -745,10 +745,10 @@ def placeEventHandler() {
|
||||
|
||||
if (deviceWrapper) {
|
||||
deviceWrapper.generatePresenceEvent(presenceState)
|
||||
log.debug "Life360 event raised on child device: ${externalId}"
|
||||
log.debug "Event raised on child device: ${externalId}"
|
||||
}
|
||||
else {
|
||||
log.warn "Life360 couldn't find child device associated with inbound Life360 event."
|
||||
log.debug "Couldn't find child device associated with inbound Life360 event."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -346,7 +346,7 @@ def devicesList(selector = '') {
|
||||
if (resp.status == 200) {
|
||||
return resp.data
|
||||
} else {
|
||||
log.debug("No response from device list call. ${resp.status} ${resp.data}")
|
||||
log.error("Non-200 from device list call. ${resp.status} ${resp.data}")
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -418,15 +418,9 @@ def updateDevices() {
|
||||
}
|
||||
getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each {
|
||||
log.info("Deleting ${it.deviceNetworkId}")
|
||||
if (state.devices[it.deviceNetworkId])
|
||||
state.devices[it.deviceNetworkId] = null
|
||||
// The reason the implementation is trying to delete this bulb is because it is not longer connected to the LIFX location.
|
||||
// Adding "try" will prevent this exception from happening.
|
||||
// Ideally device health would show to the user that the device is not longer accessible so that the user can either force delete it or remove it from the SmartApp.
|
||||
try {
|
||||
deleteChildDevice(it.deviceNetworkId)
|
||||
} catch (Exception e) {
|
||||
log.debug("Can't remove this device because it's being used by an SmartApp")
|
||||
}
|
||||
state.devices[it.deviceNetworkId] = null
|
||||
deleteChildDevice(it.deviceNetworkId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user