mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 21:03:00 +00:00
Compare commits
102 Commits
MSA-1327-2
...
PROD_2016.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f97d1a3ef | ||
|
|
255185ee8c | ||
|
|
4fd5cc5d70 | ||
|
|
236c37290e | ||
|
|
d17bc1869f | ||
|
|
534118a47a | ||
|
|
842f39e9ff | ||
|
|
dd308f6d8f | ||
|
|
1a72158a9a | ||
|
|
94ab309335 | ||
|
|
f4571289a5 | ||
|
|
b9b0cc6b37 | ||
|
|
34ad221f5d | ||
|
|
7d8acd5dd6 | ||
|
|
dba8ea7b99 | ||
|
|
49eec58de2 | ||
|
|
f2d635ab44 | ||
|
|
a7cd9e072b | ||
|
|
b70706f150 | ||
|
|
0d0a3f5ebb | ||
|
|
be7fad76fc | ||
|
|
f0e87fa5e9 | ||
|
|
1b385afa5b | ||
|
|
b5843acc38 | ||
|
|
863c49ffd4 | ||
|
|
4e5d1f6ad0 | ||
|
|
2f0d8d814b | ||
|
|
a86eba494f | ||
|
|
2549372bb7 | ||
|
|
38cdde7479 | ||
|
|
853f616cc8 | ||
|
|
2b3a4e1278 | ||
|
|
825e693efd | ||
|
|
686c8f7337 | ||
|
|
11f4e42fe9 | ||
|
|
bc459ae178 | ||
|
|
1392b6e1a5 | ||
|
|
3db96faa00 | ||
|
|
399fbcb676 | ||
|
|
eed1ced71b | ||
|
|
d080833d5c | ||
|
|
e998528e8e | ||
|
|
3472ee329d | ||
|
|
577b127287 | ||
|
|
d85566bb98 | ||
|
|
5f41af35e2 | ||
|
|
e1a5b4dd27 | ||
|
|
a2baa37901 | ||
|
|
922ab45343 | ||
|
|
962774996e | ||
|
|
d79594cbcb | ||
|
|
bf8fe4cad7 | ||
|
|
65752ce378 | ||
|
|
95f08aeb3d | ||
|
|
cd7bc1b262 | ||
|
|
be7f6a76a9 | ||
|
|
10e5b7e9d7 | ||
|
|
fc38c534f9 | ||
|
|
90e6dc91eb | ||
|
|
2e502024a6 | ||
|
|
fd47bcb8a8 | ||
|
|
972599b1b5 | ||
|
|
f5d3cca6a0 | ||
|
|
6d3ae11e44 | ||
|
|
dd63e76dfb | ||
|
|
7d6f37d98f | ||
|
|
a015742d65 | ||
|
|
3ee8f86aa3 | ||
|
|
9b285ec93b | ||
|
|
23f66e3caa | ||
|
|
7a44c59581 | ||
|
|
4d343d9bcf | ||
|
|
777f8f7e20 | ||
|
|
de6d84acd2 | ||
|
|
eac48382e8 | ||
|
|
d1a910f11f | ||
|
|
9f09a4b0b2 | ||
|
|
3c47fe7b60 | ||
|
|
45a0822e9b | ||
|
|
8cc87f3858 | ||
|
|
e818695947 | ||
|
|
bbba20288e | ||
|
|
ff9dd3f6e2 | ||
|
|
a94a62d34c | ||
|
|
e861d3c256 | ||
|
|
7adff88d0f | ||
|
|
ad1f1b2dc9 | ||
|
|
8d8b039dda | ||
|
|
c875547942 | ||
|
|
1676a9c381 | ||
|
|
49d293e749 | ||
|
|
5d1b033486 | ||
|
|
31f77513da | ||
|
|
98d7829d1a | ||
|
|
c6f706e47a | ||
|
|
532afd7336 | ||
|
|
ac7f1a0c66 | ||
|
|
cb26f055d7 | ||
|
|
34107f935e | ||
|
|
cc2d19e951 | ||
|
|
038d770691 | ||
|
|
ce28ec2039 |
@@ -22,12 +22,14 @@ metadata {
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
attribute "tamper", "enum", ["detected", "clear"]
|
||||
attribute "batteryStatus", "string"
|
||||
attribute "powerSupply", "enum", ["USB Cable", "Battery"]
|
||||
|
||||
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
|
||||
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A,0x5A"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -326,6 +328,9 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// allow device user configured or default 16 min to check in; double the periodic reporting interval
|
||||
sendEvent(name: "checkInterval", value: 2* (timeOptionValueMap[reportInterval] ?: (2*8*60)), displayed: false)
|
||||
|
||||
// This sensor joins as a secure device if you double-click the button to include it
|
||||
log.debug "${device.displayName} is configuring its settings"
|
||||
def request = []
|
||||
@@ -352,7 +357,7 @@ def configure() {
|
||||
motionSensitivity == "minimum" ? 0 : 64)
|
||||
|
||||
//5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
|
||||
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: 8*60) //association group 1
|
||||
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: (8*60)) //association group 1
|
||||
|
||||
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ metadata {
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
command "configureAfterSecure"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
|
||||
}
|
||||
@@ -245,6 +248,8 @@ def configureAfterSecure() {
|
||||
def configure() {
|
||||
// log.debug "configure()"
|
||||
//["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
|
||||
// allow device 16 min to check in; double the periodic reporting interval
|
||||
sendEvent(name: "checkInterval", value: 2*8*60, displayed: false)
|
||||
}
|
||||
|
||||
private setConfigured() {
|
||||
|
||||
@@ -20,6 +20,7 @@ metadata {
|
||||
capability "Illuminance Measurement"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86"
|
||||
}
|
||||
@@ -180,6 +181,9 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// allow device 10 min to check in; double the periodic reporting interval
|
||||
sendEvent(name: "checkInterval", value: 2*5*60, displayed: false)
|
||||
|
||||
delayBetween([
|
||||
// send binary sensor report instead of basic set for motion
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(),
|
||||
|
||||
@@ -64,8 +64,10 @@ metadata {
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
||||
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff"
|
||||
state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821"
|
||||
state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
|
||||
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821"
|
||||
}
|
||||
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
|
||||
state "station1", label:'${currentValue}', action:"preset1"
|
||||
@@ -747,8 +749,16 @@ def cb_boseSetInput(xml, input) {
|
||||
*/
|
||||
def boseSetPowerState(boolean enable) {
|
||||
log.info "boseSetPowerState(${enable})"
|
||||
queueCallback('nowPlaying', "cb_boseSetPowerState", enable ? "POWERON" : "POWEROFF")
|
||||
return boseRefreshNowPlaying()
|
||||
// Fix to get faster update of power status back from speaker after sending on/off
|
||||
// Instead of queuing the command to be sent after the refresh send it directly via sendHubCommand
|
||||
// Note: This is a temporary hack that should be replaced by a re-design of the
|
||||
// DTH to use sendHubCommand for all commands
|
||||
sendHubCommand(bosePOST("/key", "<key state=\"press\" sender=\"Gabbo\">POWER</key>"))
|
||||
sendHubCommand(bosePOST("/key", "<key state=\"release\" sender=\"Gabbo\">POWER</key>"))
|
||||
sendHubCommand(boseGET("/now_playing"))
|
||||
if (enable) {
|
||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", 5)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -787,10 +797,11 @@ def cb_boseSetPowerState(xml, state) {
|
||||
*/
|
||||
def cb_boseConfirmPowerOn(xml, tries) {
|
||||
def result = []
|
||||
log.warn "boseConfirmPowerOn() attempt #" + tries
|
||||
if (xml.attributes()['source'] == "STANDBY" && tries > 0) {
|
||||
def attempt = tries as Integer
|
||||
log.warn "boseConfirmPowerOn() attempt #$attempt"
|
||||
if (xml.attributes()['source'] == "STANDBY" && attempt > 0) {
|
||||
result << boseRefreshNowPlaying()
|
||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", tries-1)
|
||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", attempt-1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint inClusters: "0x26"
|
||||
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 {
|
||||
@@ -42,6 +44,10 @@ metadata {
|
||||
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
|
||||
}
|
||||
|
||||
preferences {
|
||||
input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
@@ -70,11 +76,28 @@ metadata {
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "level", "indicator", "refresh"])
|
||||
details(["switch", "level", "refresh"])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def updated(){
|
||||
switch (ledIndicator) {
|
||||
case "on":
|
||||
indicatorWhenOn()
|
||||
break
|
||||
case "off":
|
||||
indicatorWhenOff()
|
||||
break
|
||||
case "never":
|
||||
indicatorNever()
|
||||
break
|
||||
default:
|
||||
indicatorWhenOn()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def result = null
|
||||
if (description != "updated") {
|
||||
@@ -202,19 +225,19 @@ def refresh() {
|
||||
delayBetween(commands,100)
|
||||
}
|
||||
|
||||
def indicatorWhenOn() {
|
||||
sendEvent(name: "indicatorStatus", value: "when on")
|
||||
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
|
||||
void indicatorWhenOn() {
|
||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
def indicatorWhenOff() {
|
||||
sendEvent(name: "indicatorStatus", value: "when off")
|
||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
|
||||
void indicatorWhenOff() {
|
||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
def indicatorNever() {
|
||||
sendEvent(name: "indicatorStatus", value: "never")
|
||||
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
|
||||
void indicatorNever() {
|
||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
def invertSwitch(invert=true) {
|
||||
@@ -224,4 +247,4 @@ def invertSwitch(invert=true) {
|
||||
else {
|
||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,8 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,8 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ metadata {
|
||||
capability "Temperature Measurement"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
}
|
||||
@@ -73,7 +73,7 @@ metadata {
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
@@ -87,10 +87,10 @@ def parse(String description) {
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
@@ -128,7 +128,7 @@ private Map parseCatchAllMessage(String description) {
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
@@ -141,7 +141,7 @@ private Map parseReportAttributeMessage(String description) {
|
||||
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)
|
||||
@@ -153,11 +153,11 @@ private Map parseReportAttributeMessage(String description) {
|
||||
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: ')) {
|
||||
@@ -170,7 +170,7 @@ private Map parseCustomMessage(String description) {
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
@@ -240,7 +240,8 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +338,8 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
@@ -403,39 +404,21 @@ def refresh() {
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||
refreshCmds = refreshCmds + [
|
||||
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
/* 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 = refreshCmds + [
|
||||
/* sensitivity - default value (8) */
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
||||
}
|
||||
|
||||
//Common refresh commands
|
||||
refreshCmds = refreshCmds + [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global read 0xFC02 0x0010",
|
||||
"send 0x${device.deviceNetworkId} 1 1","delay 400"
|
||||
]
|
||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
||||
zigbee.readAttribute(0x0001, 0x0020) +
|
||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
||||
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
@@ -443,38 +426,15 @@ def refresh() {
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
def configCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
def configCmds = enrollResponse() +
|
||||
zigbee.batteryConfig() +
|
||||
zigbee.temperatureConfig() +
|
||||
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 configCmds + refresh()
|
||||
}
|
||||
|
||||
@@ -234,7 +234,8 @@ def getTemperature(value) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
|
||||
@@ -13,32 +13,34 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Contact Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
capability "Health Check"
|
||||
capability "Sensor"
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
|
||||
}
|
||||
|
||||
|
||||
simulator {
|
||||
|
||||
|
||||
}
|
||||
|
||||
preferences {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||
@@ -71,10 +73,10 @@ metadata {
|
||||
details(["contact","temperature","battery","refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
@@ -88,10 +90,10 @@ def parse(String description) {
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
@@ -99,7 +101,7 @@ def parse(String description) {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
@@ -125,7 +127,7 @@ private Map parseCatchAllMessage(String description) {
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
@@ -135,14 +137,14 @@ private boolean shouldProcessMessage(cluster) {
|
||||
private int getHumidity(value) {
|
||||
return Math.round(Double.parseDouble(value))
|
||||
}
|
||||
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -151,10 +153,10 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -167,7 +169,7 @@ private Map parseCustomMessage(String description) {
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
@@ -200,7 +202,7 @@ private Map parseIasMessage(String description) {
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
@@ -213,11 +215,11 @@ def getTemperature(value) {
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
|
||||
def result = [
|
||||
name: 'battery'
|
||||
]
|
||||
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
@@ -228,7 +230,8 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
@@ -273,6 +276,7 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
@@ -205,7 +205,8 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Battery"
|
||||
@@ -21,28 +21,28 @@ metadata {
|
||||
capability "Contact Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "Visonic", model: "MCT-340 SMA"
|
||||
}
|
||||
|
||||
|
||||
simulator {
|
||||
|
||||
|
||||
}
|
||||
|
||||
preferences {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
|
||||
|
||||
tiles {
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
}
|
||||
|
||||
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false) {
|
||||
state "temperature", label:'${currentValue}°',
|
||||
backgroundColors:[
|
||||
@@ -58,23 +58,23 @@ metadata {
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
|
||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
|
||||
|
||||
main (["contact", "temperature"])
|
||||
details(["contact","temperature","battery","refresh","configure"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
@@ -88,10 +88,10 @@ def parse(String description) {
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
@@ -99,7 +99,7 @@ def parse(String description) {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
@@ -125,20 +125,20 @@ private Map parseCatchAllMessage(String description) {
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -147,10 +147,10 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -163,7 +163,7 @@ private Map parseCustomMessage(String description) {
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
@@ -196,7 +196,7 @@ private Map parseIasMessage(String description) {
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
@@ -209,11 +209,11 @@ def getTemperature(value) {
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
|
||||
def result = [
|
||||
name: 'battery'
|
||||
]
|
||||
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (volts > 3.5) {
|
||||
@@ -223,7 +223,8 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
@@ -261,7 +262,7 @@ def refresh()
|
||||
{
|
||||
log.debug "Refreshing Temperature and Battery"
|
||||
[
|
||||
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
|
||||
|
||||
@@ -274,24 +275,24 @@ def configure() {
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
def configCmds = [
|
||||
"delay 1000",
|
||||
|
||||
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
|
||||
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
|
||||
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
|
||||
|
||||
|
||||
//"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||
//"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
|
||||
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}",
|
||||
|
||||
|
||||
"delay 500"
|
||||
]
|
||||
return configCmds + enrollResponse() + refresh() // send refresh cmds as part of config
|
||||
@@ -299,11 +300,11 @@ def configure() {
|
||||
|
||||
def enrollResponse() {
|
||||
log.debug "Sending enroll response"
|
||||
[
|
||||
|
||||
[
|
||||
|
||||
"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1"
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
private hex(value) {
|
||||
|
||||
@@ -20,6 +20,7 @@ metadata {
|
||||
capability "Switch"
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Z-Wave Dimmer Switch Generic", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer"
|
||||
}
|
||||
|
||||
simulator {
|
||||
status "on": "command: 2003, payload: FF"
|
||||
status "off": "command: 2003, payload: 00"
|
||||
status "09%": "command: 2003, payload: 09"
|
||||
status "10%": "command: 2003, payload: 0A"
|
||||
status "33%": "command: 2003, payload: 21"
|
||||
status "66%": "command: 2003, payload: 42"
|
||||
status "99%": "command: 2003, payload: 63"
|
||||
|
||||
// reply messages
|
||||
reply "2001FF,delay 5000,2602": "command: 2603, payload: FF"
|
||||
reply "200100,delay 5000,2602": "command: 2603, payload: 00"
|
||||
reply "200119,delay 5000,2602": "command: 2603, payload: 19"
|
||||
reply "200132,delay 5000,2602": "command: 2603, payload: 32"
|
||||
reply "20014B,delay 5000,2602": "command: 2603, payload: 4B"
|
||||
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
|
||||
}
|
||||
|
||||
tiles(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"
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "level", label:'${currentValue} %', unit:"%", backgroundColor:"#ffffff"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "level", "refresh"])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def result = null
|
||||
if (description != "updated") {
|
||||
log.debug "parse() >> zwave.parse($description)"
|
||||
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
|
||||
result = [result, response(zwave.basicV1.basicGet())]
|
||||
log.debug "Was hailed: requesting state update"
|
||||
} else {
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd) {
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd) {
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
private dimmerEvents(physicalgraph.zwave.Command cmd) {
|
||||
def value = (cmd.value ? "on" : "off")
|
||||
def result = [createEvent(name: "switch", value: value)]
|
||||
if (cmd.value && cmd.value <= 100) {
|
||||
result << createEvent(name: "level", value: cmd.value, unit: "%")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
|
||||
log.debug "ConfigurationReport $cmd"
|
||||
def value = "when off"
|
||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
||||
createEvent([name: "indicatorStatus", value: value])
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||
createEvent([name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false])
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
||||
log.debug "productId: ${cmd.productId}"
|
||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
updateDataValue("MSR", msr)
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd) {
|
||||
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
// Handles all Z-Wave commands we aren't interested in
|
||||
[:]
|
||||
}
|
||||
|
||||
def on() {
|
||||
delayBetween([
|
||||
zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
],5000)
|
||||
}
|
||||
|
||||
def off() {
|
||||
delayBetween([
|
||||
zwave.basicV1.basicSet(value: 0x00).format(),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
],5000)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
log.debug "setLevel >> value: $value"
|
||||
def valueaux = value as Integer
|
||||
def level = Math.max(Math.min(valueaux, 99), 0)
|
||||
if (level > 0) {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
} else {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
sendEvent(name: "level", value: level, unit: "%")
|
||||
delayBetween ([zwave.basicV1.basicSet(value: level).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
||||
}
|
||||
|
||||
def setLevel(value, duration) {
|
||||
log.debug "setLevel >> value: $value, duration: $duration"
|
||||
def valueaux = value as Integer
|
||||
def level = Math.max(Math.min(valueaux, 99), 0)
|
||||
def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
|
||||
def getStatusDelay = duration < 128 ? (duration*1000)+2000 : (Math.round(duration / 60)*60*1000)+2000
|
||||
delayBetween ([zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format(),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()], getStatusDelay)
|
||||
}
|
||||
|
||||
def poll() {
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh() is called"
|
||||
def commands = []
|
||||
commands << zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
if (getDataValue("MSR") == null) {
|
||||
commands << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
|
||||
}
|
||||
delayBetween(commands,100)
|
||||
}
|
||||
|
||||
def invertSwitch(invert=true) {
|
||||
if (invert) {
|
||||
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
|
||||
}
|
||||
else {
|
||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ metadata {
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98"
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x72,0x5A,0x80,0x73,0x86,0x84,0x85,0x59,0x71,0x70,0x7A,0x98" // Vision door/window
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -36,6 +35,7 @@ metadata {
|
||||
// status messages
|
||||
status "open": "command: 2001, payload: FF"
|
||||
status "closed": "command: 2001, payload: 00"
|
||||
status "wake up": "command: 8407, payload: "
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
@@ -81,7 +81,7 @@ def updated() {
|
||||
def cmds = []
|
||||
if (!state.MSR) {
|
||||
cmds = [
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
|
||||
command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()),
|
||||
"delay 1200",
|
||||
zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
||||
]
|
||||
@@ -94,9 +94,9 @@ def updated() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
delayBetween([
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
|
||||
batteryGetCommand()
|
||||
commands([
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
|
||||
zwave.batteryV1.batteryGet()
|
||||
], 6000)
|
||||
}
|
||||
|
||||
@@ -147,12 +147,11 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x03) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
|
||||
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||
} else if (cmd.event == 0x07) {
|
||||
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
||||
result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion")
|
||||
}
|
||||
} else if (cmd.notificationType) {
|
||||
@@ -170,12 +169,11 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||
def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
|
||||
def cmds = []
|
||||
if (!state.MSR) {
|
||||
cmds << zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId).format()
|
||||
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()
|
||||
cmds << command(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
cmds << "delay 1200"
|
||||
}
|
||||
if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) {
|
||||
cmds << batteryGetCommand()
|
||||
cmds << command(zwave.batteryV1.batteryGet())
|
||||
} else {
|
||||
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
||||
}
|
||||
@@ -212,7 +210,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
||||
if (msr == "0086-0102-0059") {
|
||||
result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format())
|
||||
} else {
|
||||
result << response(batteryGetCommand())
|
||||
result << response(command(zwave.batteryV1.batteryGet()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +218,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1])
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1])
|
||||
// log.debug "encapsulated: $encapsulatedCommand"
|
||||
if (encapsulatedCommand) {
|
||||
state.sec = 1
|
||||
@@ -232,12 +230,16 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)
|
||||
}
|
||||
|
||||
def batteryGetCommand() {
|
||||
def cmd = zwave.batteryV1.batteryGet()
|
||||
if (state.sec) {
|
||||
cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)
|
||||
private command(physicalgraph.zwave.Command cmd) {
|
||||
if (state.sec == 1) {
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
} else {
|
||||
cmd.format()
|
||||
}
|
||||
cmd.format()
|
||||
}
|
||||
|
||||
private commands(commands, delay=200) {
|
||||
delayBetween(commands.collect{ command(it) }, delay)
|
||||
}
|
||||
|
||||
def retypeBasedOnMSR() {
|
||||
@@ -260,8 +262,12 @@ def retypeBasedOnMSR() {
|
||||
setDeviceType("3-in-1 Multisensor Plus (SG)")
|
||||
break
|
||||
case "0109-2001-0106": // Vision door/window
|
||||
log.debug "Changing device type to Door / Window Sensor Plus (SG)"
|
||||
setDeviceType("Door / Window Sensor Plus (SG)")
|
||||
log.debug "Changing device type to Z-Wave Plus Door/Window Sensor"
|
||||
setDeviceType("Z-Wave Plus Door/Window Sensor")
|
||||
break
|
||||
case "0109-2002-0205": // Vision Motion
|
||||
log.debug "Changing device type to Z-Wave Plus Motion/Temp Sensor"
|
||||
setDeviceType("Z-Wave Plus Motion/Temp Sensor")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,13 @@ metadata {
|
||||
capability "Motion Sensor"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
|
||||
fingerprint mfr: "011F", prod: "0001", model: "0001", deviceJoinName: "Schlage Motion Sensor" // Schlage motion
|
||||
fingerprint mfr: "014A", prod: "0001", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion
|
||||
fingerprint mfr: "014A", prod: "0004", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion +
|
||||
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
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -125,9 +132,9 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||
}
|
||||
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
||||
result << response(zwave.batteryV1.batteryGet())
|
||||
result << response("delay 1200")
|
||||
} else {
|
||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
}
|
||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings
|
||||
* Copyright 2015 AstraLink
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Z-Wave Plus Door/Window Sensor, ZD2102*-5
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Z-Wave Plus Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Contact Sensor"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Sensor"
|
||||
|
||||
// for Astralink
|
||||
attribute "ManufacturerCode", "string"
|
||||
attribute "ProduceTypeCode", "string"
|
||||
attribute "ProductCode", "string"
|
||||
attribute "WakeUp", "string"
|
||||
attribute "WirelessConfig", "string"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x70, 0x84, 0x7A"
|
||||
fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,71"
|
||||
fingerprint mfr:"0109", prod:"2001", model:"0106" // not using deviceJoinName because it's sold under different brand names
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main (["contact"])
|
||||
details(["contact","battery"])
|
||||
}
|
||||
|
||||
simulator {
|
||||
// messages the device returns in response to commands it receives
|
||||
status "open (basic)" : "command: 9881, payload: 00 20 01 FF"
|
||||
status "closed (basic)" : "command: 9881 payload: 00 20 01 00"
|
||||
status "open (notification)" : "command: 9881, payload: 00 71 05 06 FF 00 FF 06 16 00 00"
|
||||
status "closed (notification)" : "command: 9881, payload: 00 71 05 06 00 00 FF 06 17 00 00"
|
||||
status "tamper: enclosure opened" : "command: 9881, payload: 00 71 05 07 FF 00 FF 07 03 00 00"
|
||||
status "tamper: enclosure replaced" : "command: 9881, payload: 00 71 05 07 00 00 FF 07 00 00 00"
|
||||
status "wake up" : "command: 9881, payload: 00 84 07"
|
||||
status "battery (100%)" : "command: 9881, payload: 00 80 03 64"
|
||||
status "battery low" : "command: 9881, payload: 00 80 03 FF"
|
||||
}
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "configure()"
|
||||
def cmds = []
|
||||
|
||||
if (state.sec != 1) {
|
||||
// secure inclusion may not be complete yet
|
||||
cmds << "delay 1000"
|
||||
}
|
||||
|
||||
cmds += secureSequence([
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
|
||||
zwave.batteryV1.batteryGet(),
|
||||
], 500)
|
||||
|
||||
cmds << "delay 8000"
|
||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
return cmds
|
||||
}
|
||||
|
||||
private getCommandClassVersions() {
|
||||
[
|
||||
0x71: 3, // Notification
|
||||
0x5E: 2, // ZwaveplusInfo
|
||||
0x59: 1, // AssociationGrpInfo
|
||||
0x85: 2, // Association
|
||||
0x20: 1, // Basic
|
||||
0x80: 1, // Battery
|
||||
0x70: 1, // Configuration
|
||||
0x5A: 1, // DeviceResetLocally
|
||||
0x7A: 2, // FirmwareUpdateMd
|
||||
0x72: 2, // ManufacturerSpecific
|
||||
0x73: 1, // Powerlevel
|
||||
0x98: 1, // Security
|
||||
0x84: 2, // WakeUp
|
||||
0x86: 1, // Version
|
||||
]
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
def result = []
|
||||
def cmd
|
||||
if (description.startsWith("Err 106")) {
|
||||
state.sec = 0
|
||||
result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT",
|
||||
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
|
||||
} else if (description.startsWith("Err")) {
|
||||
result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true)
|
||||
} else {
|
||||
cmd = zwave.parse(description, commandClassVersions)
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
if (result instanceof List) {
|
||||
result = result.flatten()
|
||||
}
|
||||
|
||||
log.debug "Parsed '$description' to $result"
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
|
||||
log.debug "encapsulated: $encapsulatedCommand"
|
||||
if (encapsulatedCommand) {
|
||||
state.sec = 1
|
||||
return zwaveEvent(encapsulatedCommand)
|
||||
} else {
|
||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
||||
return [createEvent(descriptionText: cmd.toString())]
|
||||
}
|
||||
}
|
||||
|
||||
def sensorValueEvent(value) {
|
||||
if (value) {
|
||||
createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open")
|
||||
} else {
|
||||
createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
return sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
||||
return sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
|
||||
return sensorValueEvent(cmd.sensorValue)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
|
||||
return sensorValueEvent(cmd.sensorState)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
def result = []
|
||||
if (cmd.notificationType == 0x06 && cmd.event == 0x16) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.notificationType == 0x06 && cmd.event == 0x17) {
|
||||
result << sensorValueEvent(0)
|
||||
} else if (cmd.notificationType == 0x07) {
|
||||
if (cmd.event == 0x00) {
|
||||
if (cmd.eventParametersLength == 0 || cmd.eventParameter[0] != 3) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false)
|
||||
} else {
|
||||
result << sensorValueEvent(0)
|
||||
}
|
||||
} else if (cmd.event == 0x01 || cmd.event == 0x02) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x03) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
if (!device.currentState("ManufacturerCode")) {
|
||||
result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
||||
}
|
||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||
} else {
|
||||
result << createEvent(descriptionText: "$device.displayName event $cmd.event ${cmd.eventParameter.inspect()}", isStateChange: true, displayed: false)
|
||||
}
|
||||
} else if (cmd.notificationType) {
|
||||
result << createEvent(descriptionText: "$device.displayName notification $cmd.notificationType event $cmd.event ${cmd.eventParameter.inspect()}", isStateChange: true, displayed: false)
|
||||
} else {
|
||||
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, isStateChange: true, displayed: false)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
||||
def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false) // for Astralink
|
||||
def cmds = []
|
||||
|
||||
if (!device.currentState("ManufacturerCode")) {
|
||||
cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
cmds << "delay 2000"
|
||||
}
|
||||
if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) {
|
||||
event.descriptionText += ", requesting battery"
|
||||
cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1))
|
||||
cmds << "delay 800"
|
||||
cmds << secure(zwave.batteryV1.batteryGet())
|
||||
cmds << "delay 2000"
|
||||
} else {
|
||||
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
|
||||
}
|
||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
|
||||
return [event, response(cmds)]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
map.descriptionText = "${device.displayName} has a low battery"
|
||||
map.isStateChange = true
|
||||
} else {
|
||||
map.value = cmd.batteryLevel
|
||||
}
|
||||
def event = createEvent(map)
|
||||
|
||||
// Save at least one battery report in events list every few days
|
||||
if (!event.isStateChange && (now() - 3*24*60*60*1000) > device.latestState("battery")?.date?.time) {
|
||||
map.isStateChange = true
|
||||
}
|
||||
state.lastbat = now()
|
||||
return [event]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
def result = []
|
||||
def manufacturerCode = String.format("%04X", cmd.manufacturerId)
|
||||
def productTypeCode = String.format("%04X", cmd.productTypeId)
|
||||
def productCode = String.format("%04X", cmd.productId)
|
||||
def wirelessConfig = "ZWP"
|
||||
log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}"
|
||||
|
||||
result << createEvent(name: "ManufacturerCode", value: manufacturerCode)
|
||||
result << createEvent(name: "ProduceTypeCode", value: productTypeCode)
|
||||
result << createEvent(name: "ProductCode", value: productCode)
|
||||
result << createEvent(name: "WirelessConfig", value: wirelessConfig)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)]
|
||||
}
|
||||
|
||||
private secure(physicalgraph.zwave.Command cmd) {
|
||||
if (state.sec == 0) { // default to secure
|
||||
cmd.format()
|
||||
} else {
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
}
|
||||
}
|
||||
|
||||
private secureSequence(commands, delay=200) {
|
||||
delayBetween(commands.collect{ secure(it) }, delay)
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings
|
||||
* Copyright 2015 AstraLink
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Z-Wave Plus Motion Sensor with Temperature Measurement, ZP3102*-5
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Z-Wave Plus Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Motion Sensor"
|
||||
capability "Temperature Measurement"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Sensor"
|
||||
|
||||
// for Astralink
|
||||
attribute "ManufacturerCode", "string"
|
||||
attribute "ProduceTypeCode", "string"
|
||||
attribute "ProductCode", "string"
|
||||
attribute "WakeUp", "string"
|
||||
attribute "WirelessConfig", "string"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x31, 0x70, 0x84, 0x7A"
|
||||
fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,31,71"
|
||||
fingerprint mfr:"0109", prod:"2002", model:"0205" // not using deviceJoinName because it's sold under different brand names
|
||||
}
|
||||
|
||||
tiles {
|
||||
standardTile("motion", "device.motion", width: 3, height: 2) {
|
||||
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
|
||||
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false) {
|
||||
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", inactiveLabel: false, decoration: "flat") {
|
||||
state "battery", label:'${currentValue}% battery', unit:"%"
|
||||
}
|
||||
|
||||
main(["motion", "temperature"])
|
||||
details(["motion", "temperature", "battery"])
|
||||
}
|
||||
}
|
||||
|
||||
def updated() {
|
||||
if (!device.currentState("ManufacturerCode")) {
|
||||
response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
||||
}
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "configure()"
|
||||
def cmds = []
|
||||
|
||||
if (state.sec != 1) {
|
||||
// secure inclusion may not be complete yet
|
||||
cmds << "delay 1000"
|
||||
}
|
||||
|
||||
cmds += secureSequence([
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
|
||||
zwave.batteryV1.batteryGet(),
|
||||
zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)
|
||||
], 500)
|
||||
|
||||
cmds << "delay 8000"
|
||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
return cmds
|
||||
}
|
||||
|
||||
private getCommandClassVersions() {
|
||||
[
|
||||
0x71: 3, // Notification
|
||||
0x5E: 2, // ZwaveplusInfo
|
||||
0x59: 1, // AssociationGrpInfo
|
||||
0x85: 2, // Association
|
||||
0x20: 1, // Basic
|
||||
0x80: 1, // Battery
|
||||
0x70: 1, // Configuration
|
||||
0x5A: 1, // DeviceResetLocally
|
||||
0x7A: 2, // FirmwareUpdateMd
|
||||
0x72: 2, // ManufacturerSpecific
|
||||
0x73: 1, // Powerlevel
|
||||
0x98: 1, // Security
|
||||
0x31: 5, // SensorMultilevel
|
||||
0x84: 2 // WakeUp
|
||||
]
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
def result = []
|
||||
def cmd
|
||||
if (description.startsWith("Err 106")) {
|
||||
state.sec = 0
|
||||
result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT",
|
||||
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
|
||||
} else if (description.startsWith("Err")) {
|
||||
result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true)
|
||||
} else {
|
||||
cmd = zwave.parse(description, commandClassVersions)
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
if (result instanceof List) {
|
||||
result = result.flatten()
|
||||
}
|
||||
|
||||
log.debug "Parsed '$description' to $result"
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
|
||||
log.debug "encapsulated: $encapsulatedCommand"
|
||||
if (encapsulatedCommand) {
|
||||
state.sec = 1
|
||||
return zwaveEvent(encapsulatedCommand)
|
||||
} else {
|
||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
||||
return [createEvent(descriptionText: cmd.toString())]
|
||||
}
|
||||
}
|
||||
|
||||
def sensorValueEvent(value) {
|
||||
def result = []
|
||||
if (value) {
|
||||
log.debug "sensorValueEvent($value) : active"
|
||||
result << createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
|
||||
} else {
|
||||
log.debug "sensorValueEvent($value) : inactive"
|
||||
result << createEvent(name: "motion", value: "inactive", descriptionText: "$device.displayName motion has stopped")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
return sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
||||
return sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
|
||||
return sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
|
||||
return sensorValueEvent(cmd.sensorValue)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
|
||||
return sensorValueEvent(cmd.sensorState)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
def result = []
|
||||
if (cmd.notificationType == 0x07) {
|
||||
if (cmd.event == 0x01 || cmd.event == 0x02) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x03) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||
} else if (cmd.event == 0x07) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x08) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x00) {
|
||||
if (cmd.eventParametersLength && cmd.eventParameter[0] == 3) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false)
|
||||
} else {
|
||||
result << sensorValueEvent(0)
|
||||
}
|
||||
} else if (cmd.event == 0xFF) {
|
||||
result << sensorValueEvent(1)
|
||||
} else {
|
||||
result << createEvent(descriptionText: "$device.displayName sent event $cmd.event")
|
||||
}
|
||||
} else if (cmd.notificationType) {
|
||||
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
|
||||
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
|
||||
} else {
|
||||
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
||||
def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false) // for Astralink
|
||||
def cmds = []
|
||||
|
||||
if (!device.currentState("ManufacturerCode")) {
|
||||
cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
cmds << "delay 2000"
|
||||
}
|
||||
if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) {
|
||||
event.descriptionText += ", requesting battery"
|
||||
cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1))
|
||||
cmds << "delay 800"
|
||||
cmds << secure(zwave.batteryV1.batteryGet())
|
||||
cmds << "delay 2000"
|
||||
} else {
|
||||
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
|
||||
}
|
||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
|
||||
return [event, response(cmds)]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def result = []
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
map.descriptionText = "${device.displayName} has a low battery"
|
||||
map.isStateChange = true
|
||||
} else {
|
||||
map.value = cmd.batteryLevel
|
||||
}
|
||||
def event = createEvent(map)
|
||||
|
||||
// Save at least one battery report in events list every few days
|
||||
if (!event.isStateChange && (now() - 3*24*60*60*1000) > device.latestState("battery")?.date?.time) {
|
||||
map.isStateChange = true
|
||||
}
|
||||
state.lastbat = now()
|
||||
return [event]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
||||
def result = []
|
||||
def map = [:]
|
||||
switch (cmd.sensorType) {
|
||||
case 1:
|
||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
||||
map.name = "temperature"
|
||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
||||
map.unit = getTemperatureScale()
|
||||
break;
|
||||
case 3:
|
||||
map.name = "illuminance"
|
||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
||||
map.unit = "lux"
|
||||
break;
|
||||
case 5:
|
||||
map.name = "humidity"
|
||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
||||
map.unit = cmd.scale == 0 ? "%" : ""
|
||||
break;
|
||||
case 0x1E:
|
||||
map.name = "loudness"
|
||||
map.unit = cmd.scale == 1 ? "dBA" : "dB"
|
||||
map.value = cmd.scaledSensorValue.toString()
|
||||
break;
|
||||
default:
|
||||
map.descriptionText = cmd.toString()
|
||||
}
|
||||
result << createEvent(map)
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
def result = []
|
||||
def manufacturerCode = String.format("%04X", cmd.manufacturerId)
|
||||
def productTypeCode = String.format("%04X", cmd.productTypeId)
|
||||
def productCode = String.format("%04X", cmd.productId)
|
||||
def wirelessConfig = "ZWP"
|
||||
log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}"
|
||||
|
||||
result << createEvent(name: "ManufacturerCode", value: manufacturerCode)
|
||||
result << createEvent(name: "ProduceTypeCode", value: productTypeCode)
|
||||
result << createEvent(name: "ProductCode", value: productCode)
|
||||
result << createEvent(name: "WirelessConfig", value: wirelessConfig)
|
||||
|
||||
if (manufacturerCode == "0109" && productTypeCode == "2002") {
|
||||
result << response(secureSequence([
|
||||
// Change re-trigger duration to 1 minute
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 1, configurationValue: [1], size: 1),
|
||||
zwave.batteryV1.batteryGet(),
|
||||
zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)
|
||||
], 400))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)]
|
||||
}
|
||||
|
||||
private secure(physicalgraph.zwave.Command cmd) {
|
||||
if (state.sec == 0) { // default to secure
|
||||
cmd.format()
|
||||
} else {
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
}
|
||||
}
|
||||
|
||||
private secureSequence(commands, delay=200) {
|
||||
delayBetween(commands.collect{ secure(it) }, delay)
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Z-Wave Switch Generic", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
status "on": "command: 2003, payload: FF"
|
||||
status "off": "command: 2003, payload: 00"
|
||||
|
||||
// reply messages
|
||||
reply "2001FF,delay 100,2502": "command: 2503, payload: FF"
|
||||
reply "200100,delay 100,2502": "command: 2503, payload: 00"
|
||||
}
|
||||
|
||||
// tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
|
||||
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main "switch"
|
||||
details(["switch","refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def result = null
|
||||
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
|
||||
if (cmd) {
|
||||
result = createEvent(zwaveEvent(cmd))
|
||||
}
|
||||
if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
|
||||
result = [result, response(zwave.basicV1.basicGet())]
|
||||
log.debug "Was hailed: requesting state update"
|
||||
} else {
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
||||
[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
|
||||
[name: "switch", value: cmd.value ? "on" : "off", type: "digital"]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
|
||||
def value = "when off"
|
||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
||||
[name: "indicatorStatus", value: value, display: false]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||
[name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
||||
log.debug "productId: ${cmd.productId}"
|
||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
updateDataValue("MSR", msr)
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
// Handles all Z-Wave commands we aren't interested in
|
||||
[:]
|
||||
}
|
||||
|
||||
def on() {
|
||||
delayBetween([
|
||||
zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||
zwave.switchBinaryV1.switchBinaryGet().format()
|
||||
])
|
||||
}
|
||||
|
||||
def off() {
|
||||
delayBetween([
|
||||
zwave.basicV1.basicSet(value: 0x00).format(),
|
||||
zwave.switchBinaryV1.switchBinaryGet().format()
|
||||
])
|
||||
}
|
||||
|
||||
def poll() {
|
||||
delayBetween([
|
||||
zwave.switchBinaryV1.switchBinaryGet().format(),
|
||||
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
|
||||
])
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
delayBetween([
|
||||
zwave.switchBinaryV1.switchBinaryGet().format(),
|
||||
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
|
||||
])
|
||||
}
|
||||
|
||||
def invertSwitch(invert=true) {
|
||||
if (invert) {
|
||||
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
|
||||
}
|
||||
else {
|
||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,15 @@ metadata {
|
||||
definition (name: "Z-Wave Switch", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Indicator"
|
||||
capability "Switch"
|
||||
capability "Switch"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint inClusters: "0x25"
|
||||
fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch"
|
||||
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
|
||||
fingerprint mfr:"0063", prod:"5052", deviceJoinName: "Z-Wave Plug-In Switch"
|
||||
fingerprint mfr:"0113", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -33,6 +36,10 @@ metadata {
|
||||
reply "200100,delay 100,2502": "command: 2503, payload: 00"
|
||||
}
|
||||
|
||||
preferences {
|
||||
input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
|
||||
}
|
||||
|
||||
// tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
@@ -52,10 +59,27 @@ metadata {
|
||||
}
|
||||
|
||||
main "switch"
|
||||
details(["switch","refresh","indicator"])
|
||||
details(["switch","refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
def updated(){
|
||||
switch (ledIndicator) {
|
||||
case "on":
|
||||
indicatorWhenOn()
|
||||
break
|
||||
case "off":
|
||||
indicatorWhenOff()
|
||||
break
|
||||
case "never":
|
||||
indicatorNever()
|
||||
break
|
||||
default:
|
||||
indicatorWhenOn()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def result = null
|
||||
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
|
||||
@@ -139,19 +163,19 @@ def refresh() {
|
||||
])
|
||||
}
|
||||
|
||||
def indicatorWhenOn() {
|
||||
void indicatorWhenOn() {
|
||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
||||
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
def indicatorWhenOff() {
|
||||
void indicatorWhenOff() {
|
||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
def indicatorNever() {
|
||||
void indicatorNever() {
|
||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
||||
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
def invertSwitch(invert=true) {
|
||||
|
||||
@@ -337,10 +337,10 @@ def initialize() {
|
||||
|
||||
settings.devices.each {
|
||||
def deviceId = it
|
||||
def detail = state.deviceDetail[deviceId]
|
||||
def detail = state?.deviceDetail[deviceId]
|
||||
|
||||
try {
|
||||
switch(detail.type) {
|
||||
switch(detail?.type) {
|
||||
case 'NAMain':
|
||||
log.debug "Base station"
|
||||
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
|
||||
@@ -487,12 +487,12 @@ def poll() {
|
||||
log.debug "State: ${state.deviceState}"
|
||||
|
||||
settings.devices.each { deviceId ->
|
||||
def detail = state.deviceDetail[deviceId]
|
||||
def data = state.deviceState[deviceId]
|
||||
def child = children.find { it.deviceNetworkId == deviceId }
|
||||
def detail = state?.deviceDetail[deviceId]
|
||||
def data = state?.deviceState[deviceId]
|
||||
def child = children?.find { it.deviceNetworkId == deviceId }
|
||||
|
||||
log.debug "Update: $child";
|
||||
switch(detail.type) {
|
||||
switch(detail?.type) {
|
||||
case 'NAMain':
|
||||
log.debug "Updating NAMain $data"
|
||||
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
|
||||
|
||||
@@ -1,345 +0,0 @@
|
||||
/**
|
||||
* SmartThings service for Prempoint
|
||||
*
|
||||
* Author: Prempoint Inc. (c) 2016
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "Prempoint",
|
||||
namespace: "prempoint.com",
|
||||
author: "Prempoint Inc.",
|
||||
description: "SmartThings service for Prempoint",
|
||||
category: "Connections",
|
||||
iconUrl: "http://www.prempoint.com/images/social_app_emblem_50x50.png",
|
||||
iconX2Url: "http://www.prempoint.com/images/social_app_emblem_100x100.png",
|
||||
iconX3Url: "http://www.prempoint.com/images/social_app_emblem_150x150.png",
|
||||
oauth: [displayName: "Prempoint", displayLink: "http://www.prempoint.com/"])
|
||||
|
||||
preferences {
|
||||
section("Allow Prempoint to Control & Access These Things...") {
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "garagedoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false
|
||||
//input "doors", "capability.doorControl", title: "Which Doors?", multiple: true, required: false
|
||||
input "cameras", "capability.imageCapture", title: "Which Cameras?", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/list") {
|
||||
action: [
|
||||
GET: "listDevices"
|
||||
]
|
||||
}
|
||||
path("/switches") {
|
||||
action: [
|
||||
GET: "listSwitches"
|
||||
]
|
||||
}
|
||||
path("/switches/:id") {
|
||||
action: [
|
||||
GET: "showSwitch"
|
||||
]
|
||||
}
|
||||
path("/switches/:id/:command") {
|
||||
action: [
|
||||
GET: "updateSwitch"
|
||||
]
|
||||
}
|
||||
path("/switches/:id/:command/:level") {
|
||||
action: [
|
||||
GET: "updateSwitch"
|
||||
]
|
||||
}
|
||||
path("/locks") {
|
||||
action: [
|
||||
GET: "listLocks"
|
||||
]
|
||||
}
|
||||
path("/locks/:id") {
|
||||
action: [
|
||||
GET: "showLock"
|
||||
]
|
||||
}
|
||||
path("/locks/:id/:command") {
|
||||
action: [
|
||||
GET: "updateLock"
|
||||
]
|
||||
}
|
||||
path("/doors/:id") {
|
||||
action: [
|
||||
GET: "showDoor"
|
||||
]
|
||||
}
|
||||
path("/doors/:id/:command") {
|
||||
action: [
|
||||
GET: "updateDoor"
|
||||
]
|
||||
}
|
||||
path("/garagedoors/:id") {
|
||||
action: [
|
||||
GET: "showGarageDoor"
|
||||
]
|
||||
}
|
||||
path("/garagedoors/:id/:command") {
|
||||
action: [
|
||||
GET: "updateGarageDoor"
|
||||
]
|
||||
}
|
||||
path("/cameras/:id") {
|
||||
action: [
|
||||
GET: "showCamera"
|
||||
]
|
||||
}
|
||||
path("/cameras/:id/:command") {
|
||||
action: [
|
||||
GET: "updateCamera"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {}
|
||||
|
||||
def updated() {}
|
||||
|
||||
def listDevices() {
|
||||
log.debug "entering listDevices"
|
||||
//return listSwitches() + listLocks() + listGarageDoors() + listDoors() + listCameras()
|
||||
return listSwitches() + listLocks() + listGarageDoors() + listCameras()
|
||||
}
|
||||
|
||||
//switches
|
||||
def listSwitches() {
|
||||
log.debug "entering listSwitches"
|
||||
switches.collect{showDevice(it,"switch")}
|
||||
}
|
||||
|
||||
def showSwitch() {
|
||||
log.debug "entering showSwitches"
|
||||
show(switches, "switch")
|
||||
}
|
||||
|
||||
def updateSwitch() {
|
||||
log.debug "entering updateSwitches"
|
||||
update(switches, "switch")
|
||||
}
|
||||
|
||||
//locks
|
||||
def listLocks() {
|
||||
log.debug "entering listLocks"
|
||||
locks.collect{showDevice(it,"lock")}
|
||||
}
|
||||
|
||||
def showLock() {
|
||||
log.debug "entering showLock"
|
||||
show(locks, "lock")
|
||||
}
|
||||
|
||||
def updateLock() {
|
||||
log.debug "entering updateLock"
|
||||
update(locks, "lock")
|
||||
}
|
||||
|
||||
//doors
|
||||
def listDoors() {
|
||||
log.debug "entering listDoors"
|
||||
locks.collect{showDevice(it,"door")}
|
||||
}
|
||||
|
||||
def showDoor() {
|
||||
log.debug "entering showDoors"
|
||||
show(doors, "door")
|
||||
}
|
||||
|
||||
def updateDoor() {
|
||||
log.debug "entering updateDoor"
|
||||
update(doors, "door")
|
||||
}
|
||||
|
||||
//garagedoors
|
||||
def listGarageDoors() {
|
||||
log.debug "entering listGarageDoors"
|
||||
locks.collect{showDevice(it,"garagedoor")}
|
||||
}
|
||||
|
||||
def showGarageDoor() {
|
||||
log.debug "entering showGarageDoors"
|
||||
show(garagedoors, "garagedoor")
|
||||
}
|
||||
|
||||
def updateGarageDoor() {
|
||||
log.debug "entering updateGarageDoor"
|
||||
update(gargedoors, "garagedoor")
|
||||
}
|
||||
|
||||
//cameras
|
||||
def listCameras() {
|
||||
log.debug "entering listCameras"
|
||||
cameras.collect{showDevice(it,"image")}
|
||||
}
|
||||
|
||||
def showCamera() {
|
||||
log.debug "entering showCameras"
|
||||
show(cameras, "camera")
|
||||
}
|
||||
|
||||
def updateCamera() {
|
||||
log.debug "entering updateCamera"
|
||||
update(cameras, "camera")
|
||||
}
|
||||
|
||||
def deviceHandler(evt) {}
|
||||
|
||||
private update(devices, type) {
|
||||
def rc = null
|
||||
|
||||
//def command = request.JSON?.command
|
||||
def command = params.command
|
||||
|
||||
log.debug "update, request: params: ${params}, devices: $devices.id type=$type command=$command"
|
||||
|
||||
// Process the command.
|
||||
if (command)
|
||||
{
|
||||
def dev = devices.find { it.id == params.id }
|
||||
if (!dev) {
|
||||
httpError(404, "Device not found: $params.id")
|
||||
} else if (type == "switch") {
|
||||
switch(command) {
|
||||
case "on":
|
||||
rc = dev.on()
|
||||
break
|
||||
case "off":
|
||||
rc = dev.off()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
} else if (type == "lock") {
|
||||
switch(command) {
|
||||
case "lock":
|
||||
rc = dev.lock()
|
||||
break
|
||||
case "unlock":
|
||||
rc = dev.unlock()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device:=$it.id $dev")
|
||||
}
|
||||
} else if (type == "door") {
|
||||
switch(command) {
|
||||
case "open":
|
||||
rc = dev.open()
|
||||
break
|
||||
case "close":
|
||||
rc = dev.close()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
} else if (type == "garagedoor") {
|
||||
switch(command) {
|
||||
case "open":
|
||||
rc = dev.open()
|
||||
break
|
||||
case "close":
|
||||
rc = dev.close()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
} else if (type == "camera") {
|
||||
switch(command) {
|
||||
case "take":
|
||||
rc = dev.take()
|
||||
log.debug "Device command=$command device=$it.id $dev current image=$it.currentImage"
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "executed device=$it.id $dev command=$command rc=$rc"
|
||||
|
||||
// Check that the device is a switch that is currently on, supports 'setLevel"
|
||||
// and that a level was specified.
|
||||
int level = params.level ? params.level as int : -1;
|
||||
if ((type == "switch") && (dev.currentValue('switch') == "on") && hasLevel(dev) && (level != -1)) {
|
||||
log.debug "device about to setLevel=$level"
|
||||
dev.setLevel(level);
|
||||
}
|
||||
|
||||
// Show the device info if necessary.
|
||||
if (rc == null) {
|
||||
rc = showDevice(dev, type)
|
||||
}
|
||||
}
|
||||
|
||||
return rc
|
||||
}
|
||||
|
||||
private show(devices, type) {
|
||||
def dev = devices.find { it.id == params.id }
|
||||
if (!dev) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
// Show the device info.
|
||||
showDevice(dev, type)
|
||||
}
|
||||
}
|
||||
|
||||
private showDevice(it, type) {
|
||||
def props = null
|
||||
|
||||
// Get the current state for the device type.
|
||||
def state = [it.currentState(type)]
|
||||
|
||||
// Check that whether the a switch device with level support is located and update the returned device type.
|
||||
def devType = type
|
||||
|
||||
if (type == "switch" && hasLevel(it)) {
|
||||
// Assign "switchWithLevel" to device type.
|
||||
devType = "switchWithLevel"
|
||||
// Add the level state.
|
||||
def levelState = it.currentState("level")
|
||||
if (levelState) {
|
||||
state.add(levelState)
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "device label=$it.label type=$devType"
|
||||
|
||||
// Assign the device item properties if appropriate.
|
||||
if (it) {
|
||||
props = [id: it.id, label: it.label, type: devType, state: state]
|
||||
// Add the hub information to the device properties
|
||||
// if appropriate.
|
||||
if (it.hub) {
|
||||
props.put("location", it.hub.hub.location)
|
||||
}
|
||||
if (it.currentImage) {
|
||||
props.put("currentImage", it.currentImage)
|
||||
}
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
private hasLevel(device) {
|
||||
// Default return value.
|
||||
def rc = false;
|
||||
|
||||
// Get the device supported commands.
|
||||
def supportedCommands = device.supportedCommands
|
||||
|
||||
// Check to see if the "setLevel" was found and assign
|
||||
// the appropriate return value.
|
||||
if (supportedCommands) {
|
||||
// Find the "setLevel" command.
|
||||
rc = supportedCommands.toString().indexOf("setLevel") != -1
|
||||
}
|
||||
|
||||
log.debug "hasLevel device label=$device.label supportedCommands=$supportedCommands rc=$rc"
|
||||
|
||||
return rc
|
||||
}
|
||||
@@ -39,6 +39,7 @@ preferences {
|
||||
page(name: "completionPage")
|
||||
page(name: "numbersPage")
|
||||
page(name: "controllerExplanationPage")
|
||||
page(name: "unsupportedDevicesPage")
|
||||
}
|
||||
|
||||
def rootPage() {
|
||||
@@ -47,6 +48,9 @@ def rootPage() {
|
||||
section("What to dim") {
|
||||
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
|
||||
if (dimmers) {
|
||||
if (dimmersContainUnsupportedDevices()) {
|
||||
href(name: "toUnsupportedDevicesPage", page: "unsupportedDevicesPage", title: "Some of your selected dimmers don't seem to be supported", description: "Tap here to fix it", required: true)
|
||||
}
|
||||
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
|
||||
}
|
||||
}
|
||||
@@ -71,6 +75,31 @@ def rootPage() {
|
||||
}
|
||||
}
|
||||
|
||||
def unsupportedDevicesPage() {
|
||||
|
||||
def unsupportedDimmers = dimmers.findAll { !hasSetLevelCommand(it) }
|
||||
|
||||
dynamicPage(name: "unsupportedDevicesPage") {
|
||||
if (unsupportedDimmers) {
|
||||
section("These devices do not support the setLevel command") {
|
||||
unsupportedDimmers.each {
|
||||
paragraph deviceLabel(it)
|
||||
}
|
||||
}
|
||||
section {
|
||||
input(name: "dimmers", type: "capability.sensor", title: "Please remove the above devices from this list.", submitOnChange: true, multiple: true)
|
||||
}
|
||||
section {
|
||||
paragraph "If you think there is a mistake here, please contact support."
|
||||
}
|
||||
} else {
|
||||
section {
|
||||
paragraph "You're all set. You can hit the back button, now. Thanks for cleaning up your settings :)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def controllerExplanationPage() {
|
||||
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
|
||||
|
||||
@@ -208,7 +237,7 @@ def completionPage() {
|
||||
}
|
||||
|
||||
section("Notifications") {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input("recipients", "contact", title: "Send notifications to", required: false) {
|
||||
input(name: "completionPhoneNumber", type: "phone", title: "Text This Number", description: "Phone number", required: false)
|
||||
input(name: "completionPush", type: "bool", title: "Send A Push Notification", description: "Phone number", required: false)
|
||||
}
|
||||
@@ -528,14 +557,16 @@ def updateDimmers(percentComplete) {
|
||||
} else {
|
||||
|
||||
def shouldChangeColors = (colorize && colorize != "false")
|
||||
def canChangeColors = hasSetColorCommand(dimmer)
|
||||
|
||||
log.debug "Setting ${deviceLabel(dimmer)} to ${nextLevel}"
|
||||
|
||||
if (shouldChangeColors && canChangeColors) {
|
||||
dimmer.setColor([hue: getHue(dimmer, nextLevel), saturation: 100, level: nextLevel])
|
||||
} else {
|
||||
if (shouldChangeColors && hasSetColorCommand(dimmer)) {
|
||||
def hue = getHue(dimmer, nextLevel)
|
||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel} and hue to ${hue}"
|
||||
dimmer.setColor([hue: hue, saturation: 100, level: nextLevel])
|
||||
} else if (hasSetLevelCommand(dimmer)) {
|
||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel}"
|
||||
dimmer.setLevel(nextLevel)
|
||||
} else {
|
||||
log.warn "${deviceLabel(dimmer)} does not have setColor or setLevel commands."
|
||||
}
|
||||
|
||||
}
|
||||
@@ -689,7 +720,7 @@ def completionPercentage() {
|
||||
|
||||
def now = new Date().getTime()
|
||||
def timeElapsed = now - atomicState.start
|
||||
def totalRunTime = totalRunTimeMillis()
|
||||
def totalRunTime = totalRunTimeMillis() ?: 1
|
||||
def percentComplete = timeElapsed / totalRunTime * 100
|
||||
log.debug "percentComplete: ${percentComplete}"
|
||||
|
||||
@@ -817,24 +848,21 @@ private getRedHue(level) {
|
||||
if (level >= 96) return 17
|
||||
}
|
||||
|
||||
private dimmersContainUnsupportedDevices() {
|
||||
def found = dimmers.find { hasSetLevelCommand(it) == false }
|
||||
return found != null
|
||||
}
|
||||
|
||||
private hasSetLevelCommand(device) {
|
||||
def isDimmer = false
|
||||
device.supportedCommands.each {
|
||||
if (it.name.contains("setLevel")) {
|
||||
isDimmer = true
|
||||
}
|
||||
}
|
||||
return isDimmer
|
||||
return hasCommand(device, "setLevel")
|
||||
}
|
||||
|
||||
private hasSetColorCommand(device) {
|
||||
def hasColor = false
|
||||
device.supportedCommands.each {
|
||||
if (it.name.contains("setColor")) {
|
||||
hasColor = true
|
||||
}
|
||||
}
|
||||
return hasColor
|
||||
return hasCommand(device, "setColor")
|
||||
}
|
||||
|
||||
private hasCommand(device, String command) {
|
||||
return (device.supportedCommands.find { it.name == command } != null)
|
||||
}
|
||||
|
||||
private dimmersWithSetColorCommand() {
|
||||
@@ -1073,4 +1101,4 @@ def hasStartLevel() {
|
||||
|
||||
def hasEndLevel() {
|
||||
return (endLevel != null && endLevel != "")
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ definition(
|
||||
preferences {
|
||||
page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5)
|
||||
page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5)
|
||||
page(name:"bridgeDiscoveryFailed", title:"Bridge Discovery Failed", content:"bridgeDiscoveryFailed", refreshTimeout:0)
|
||||
page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5)
|
||||
page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5)
|
||||
}
|
||||
@@ -53,12 +54,21 @@ def bridgeDiscovery(params=[:])
|
||||
def options = bridges ?: []
|
||||
def numFound = options.size() ?: 0
|
||||
|
||||
if (numFound == 0 && state.bridgeRefreshCount > 25) {
|
||||
log.trace "Cleaning old bridges memory"
|
||||
state.bridges = [:]
|
||||
state.bridgeRefreshCount = 0
|
||||
app.updateSetting("selectedHue", "")
|
||||
}
|
||||
if (numFound == 0) {
|
||||
if (state.bridgeRefreshCount == 25) {
|
||||
log.trace "Cleaning old bridges memory"
|
||||
state.bridges = [:]
|
||||
app.updateSetting("selectedHue", "")
|
||||
} else if (state.bridgeRefreshCount > 100) {
|
||||
// five minutes have passed, give up
|
||||
// there seems to be a problem going back from discovey failed page in some instances (compared to pressing next)
|
||||
// however it is probably a SmartThings settings issue
|
||||
state.bridges = [:]
|
||||
app.updateSetting("selectedHue", "")
|
||||
state.bridgeRefreshCount = 0
|
||||
return bridgeDiscoveryFailed()
|
||||
}
|
||||
}
|
||||
|
||||
ssdpSubscribe()
|
||||
|
||||
@@ -79,6 +89,13 @@ def bridgeDiscovery(params=[:])
|
||||
}
|
||||
}
|
||||
|
||||
def bridgeDiscoveryFailed() {
|
||||
return dynamicPage(name:"bridgeDiscoveryFailed", title: "Bridge Discovery Failed", nextPage: "bridgeDiscovery") {
|
||||
section("Failed to discover any Hue Bridges. Please confirm that the Hue Bridge is connected to the same network as your SmartThings Hub, and that it has power.") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def bridgeLinking()
|
||||
{
|
||||
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
|
||||
@@ -88,19 +105,15 @@ def bridgeLinking()
|
||||
def nextPage = ""
|
||||
def title = "Linking with your Hue"
|
||||
def paragraphText
|
||||
def hueimage = null
|
||||
if (selectedHue) {
|
||||
paragraphText = "Press the button on your Hue Bridge to setup a link. "
|
||||
hueimage = "http://huedisco.mediavibe.nl/wp-content/uploads/2013/09/pair-bridge.png"
|
||||
} else {
|
||||
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
|
||||
hueimage = null
|
||||
}
|
||||
if (state.username) { //if discovery worked
|
||||
nextPage = "bulbDiscovery"
|
||||
title = "Success!"
|
||||
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
|
||||
hueimage = null
|
||||
}
|
||||
|
||||
if((linkRefreshcount % 2) == 0 && !state.username) {
|
||||
@@ -110,8 +123,6 @@ def bridgeLinking()
|
||||
return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) {
|
||||
section("") {
|
||||
paragraph """${paragraphText}"""
|
||||
if (hueimage != null)
|
||||
image "${hueimage}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,13 +146,14 @@ def bulbDiscovery() {
|
||||
if((bulbRefreshCount % 5) == 0) {
|
||||
discoverHueBulbs()
|
||||
}
|
||||
def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) }
|
||||
def title = selectedBridge?.value?.name ?: "Find bridges"
|
||||
|
||||
return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||
section("Please wait while we discover your Hue Bulbs. 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 Bulbs (${numFound} found)", multiple:true, options:bulboptions
|
||||
}
|
||||
section {
|
||||
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
|
||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||
|
||||
}
|
||||
@@ -348,26 +360,29 @@ def addBulbs() {
|
||||
def newHueBulb
|
||||
if (bulbs instanceof java.util.Map) {
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
if (newHueBulb != null) {
|
||||
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
|
||||
if (newHueBulb != null) {
|
||||
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
if (d) {
|
||||
log.debug "created ${d.displayName} with id $dni"
|
||||
d.completedSetup = true
|
||||
d.refresh()
|
||||
}
|
||||
} else {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
} else {
|
||||
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
|
||||
d?.refresh()
|
||||
}
|
||||
} else {
|
||||
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
|
||||
if (bulbs instanceof java.util.Map) {
|
||||
// Update device type if incorrect
|
||||
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
upgradeDeviceType(d, newHueBulb?.value?.type)
|
||||
}
|
||||
}
|
||||
@@ -399,6 +414,7 @@ def addBridge() {
|
||||
}
|
||||
if (newbridge) {
|
||||
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
|
||||
d?.completedSetup = true
|
||||
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
||||
def childDevice = getChildDevice(d.deviceNetworkId)
|
||||
childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber)
|
||||
@@ -486,7 +502,21 @@ void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
|
||||
def bridges = getHueBridges()
|
||||
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
||||
if (bridge) {
|
||||
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||
// serialNumber from API is in format of 0017882413ad (mac address), however on the actual bridge only last six
|
||||
// characters are printed on the back so using that to identify bridge
|
||||
def idNumber = body?.device?.serialNumber?.text()
|
||||
if (idNumber?.size() >= 6)
|
||||
idNumber = idNumber[-6..-1].toUpperCase()
|
||||
|
||||
// usually in form of bridge name followed by (ip), i.e. defaults to Philips Hue (192.168.1.2)
|
||||
// replace IP with serial number to make it easier for user to identify
|
||||
def name = body?.device?.friendlyName?.text()
|
||||
def index = name?.indexOf('(')
|
||||
if (index != -1) {
|
||||
name = name.substring(0,index)
|
||||
name += " ($idNumber)"
|
||||
}
|
||||
bridge.value << [name:name, serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||
} else {
|
||||
log.error "/description.xml returned a bridge that didn't exist"
|
||||
}
|
||||
@@ -659,7 +689,7 @@ def parse(childDevice, description) {
|
||||
log.warn "Parsing Body failed - trying again..."
|
||||
poll()
|
||||
}
|
||||
if (body instanceof java.util.HashMap) {
|
||||
if (body instanceof java.util.Map) {
|
||||
//poll response
|
||||
def bulbs = getChildDevices()
|
||||
for (bulb in body) {
|
||||
@@ -800,22 +830,22 @@ def setColorTemperature(childDevice, huesettings) {
|
||||
|
||||
def setColor(childDevice, huesettings) {
|
||||
log.debug "Executing 'setColor($huesettings)'"
|
||||
|
||||
|
||||
def value = [:]
|
||||
def hue = null
|
||||
def sat = null
|
||||
def xy = null
|
||||
|
||||
|
||||
if (huesettings.hex != null) {
|
||||
value.xy = getHextoXY(huesettings.hex)
|
||||
} else {
|
||||
if (huesettings.hue != null)
|
||||
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||
if (huesettings.saturation != null)
|
||||
if (huesettings.saturation != null)
|
||||
value.sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
|
||||
}
|
||||
|
||||
// Default behavior is to turn light on
|
||||
|
||||
// Default behavior is to turn light on
|
||||
value.on = true
|
||||
|
||||
if (huesettings.level != null) {
|
||||
@@ -823,7 +853,7 @@ def setColor(childDevice, huesettings) {
|
||||
value.on = false
|
||||
else if (huesettings.level == 1)
|
||||
value.bri = 1
|
||||
else
|
||||
else
|
||||
value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
|
||||
}
|
||||
value.alert = huesettings.alert ? huesettings.alert : "none"
|
||||
|
||||
@@ -658,29 +658,73 @@ def updateDevice() {
|
||||
def data = request.JSON
|
||||
def command = data.command
|
||||
def arguments = data.arguments
|
||||
|
||||
log.debug "updateDevice, params: ${params}, request: ${data}"
|
||||
if (!command) {
|
||||
render status: 400, data: '{"msg": "command is required"}'
|
||||
} else {
|
||||
def device = allDevices.find { it.id == params.id }
|
||||
if (device) {
|
||||
if (device.hasCommand("$command")) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Command not supported by this Device"}'
|
||||
}
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
if (device) {
|
||||
if (validateCommand(device, command)) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 403, data: '{"msg": "Access denied. This command is not supported by current capability."}'
|
||||
}
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(device)
|
||||
if (currentDeviceCapability != "" && capabilityCommands[currentDeviceCapability]) {
|
||||
return (command in capabilityCommands[currentDeviceCapability] || (currentDeviceCapability == "Switch" && command == "setLevel" && device.hasCommand("setLevel"))) ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(device) {
|
||||
def capName = ""
|
||||
if (switches.find{it.id == device.id})
|
||||
capName = "Switch"
|
||||
else if (alarms.find{it.id == device.id})
|
||||
capName = "Alarm"
|
||||
else if (locks.find{it.id == device.id})
|
||||
capName = "Lock"
|
||||
log.trace "Device: $device - Capability Name: $capName"
|
||||
return capName
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
def listSubscriptions() {
|
||||
log.debug "listSubscriptions()"
|
||||
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
|
||||
@@ -779,18 +823,33 @@ def deviceHandler(evt) {
|
||||
}
|
||||
|
||||
def sendToHarmony(evt, String callbackUrl) {
|
||||
def callback = new URI(callbackUrl)
|
||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
||||
sendHubCommand(new physicalgraph.device.HubAction(
|
||||
method: "POST",
|
||||
path: path,
|
||||
headers: [
|
||||
"Host": host,
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
))
|
||||
def callback = new URI(callbackUrl)
|
||||
if (callback.port != -1) {
|
||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
||||
sendHubCommand(new physicalgraph.device.HubAction(
|
||||
method: "POST",
|
||||
path: path,
|
||||
headers: [
|
||||
"Host": host,
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
))
|
||||
} else {
|
||||
def params = [
|
||||
uri: callbackUrl,
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
]
|
||||
try {
|
||||
log.debug "Sending data to Harmony Cloud: $params"
|
||||
httpPostJson(params) { resp ->
|
||||
log.debug "Harmony Cloud - Response: ${resp.status}"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "Harmony Cloud - Something went wrong: $e"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def listHubs() {
|
||||
|
||||
@@ -92,22 +92,87 @@ void updateLock() {
|
||||
|
||||
private void updateAll(devices) {
|
||||
def command = request.JSON?.command
|
||||
if (command) {
|
||||
devices."$command"()
|
||||
def type = params.param1
|
||||
if (!devices) {
|
||||
httpError(404, "Devices not found")
|
||||
}
|
||||
|
||||
if (command){
|
||||
devices.each { device ->
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void update(devices) {
|
||||
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
||||
def command = request.JSON?.command
|
||||
if (command) {
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
def type = params.param1
|
||||
def device = devices?.find { it.id == params.id }
|
||||
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
}
|
||||
|
||||
if (command) {
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, deviceType, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||
if (capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(type) {
|
||||
switch(type) {
|
||||
case "switches":
|
||||
return "Switch"
|
||||
case "locks":
|
||||
return "Lock"
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and executes the command
|
||||
* on the device or devices
|
||||
*/
|
||||
def executeCommand(device, type, command) {
|
||||
if (validateCommand(device, type, command)) {
|
||||
device."$command"()
|
||||
} else {
|
||||
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||
}
|
||||
}
|
||||
|
||||
private show(devices, name) {
|
||||
|
||||
Reference in New Issue
Block a user