mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-26 13:24:09 +00:00
Compare commits
1 Commits
MSA-1371-2
...
MSA-1339-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d289dbd6b |
@@ -0,0 +1,326 @@
|
|||||||
|
/**
|
||||||
|
* Sercomm SZ-DWS04 Open/Closed Sensor
|
||||||
|
*
|
||||||
|
* 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: "Sercomm SZ-DWS04 Open/Closed Sensor", namespace: "mikespry", author: "Mike Sprycha") {
|
||||||
|
capability "Battery"
|
||||||
|
capability "Configuration"
|
||||||
|
capability "Contact Sensor"
|
||||||
|
capability "Refresh"
|
||||||
|
capability "Temperature Measurement"
|
||||||
|
|
||||||
|
command "enrollResponse"
|
||||||
|
|
||||||
|
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "Sercomm Corp.", model: "SZ-DWS04N_SF", deviceJoinName: "Sercomm SZ-DWS04"
|
||||||
|
}
|
||||||
|
|
||||||
|
simulator {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||||
|
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles(scale: 2) {
|
||||||
|
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
|
||||||
|
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||||
|
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||||
|
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
|
||||||
|
state "temperature", label:'${currentValue}°',
|
||||||
|
backgroundColors:[
|
||||||
|
[value: 31, color: "#153591"],
|
||||||
|
[value: 44, color: "#1e9cbb"],
|
||||||
|
[value: 59, color: "#90d2a7"],
|
||||||
|
[value: 74, color: "#44b621"],
|
||||||
|
[value: 84, color: "#f1d801"],
|
||||||
|
[value: 95, color: "#d04e00"],
|
||||||
|
[value: 96, color: "#bc2323"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||||
|
state "battery", label:'${currentValue}% battery', unit:""
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
|
}
|
||||||
|
|
||||||
|
main (["contact", "temperature"])
|
||||||
|
details(["contact","temperature","battery","refresh"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def parse(String description) {
|
||||||
|
log.debug "description: $description"
|
||||||
|
|
||||||
|
Map map = [:]
|
||||||
|
if (description?.startsWith('catchall:')) {
|
||||||
|
map = parseCatchAllMessage(description)
|
||||||
|
}
|
||||||
|
else if (description?.startsWith('read attr -')) {
|
||||||
|
map = parseReportAttributeMessage(description)
|
||||||
|
}
|
||||||
|
else if (description?.startsWith('temperature: ')) {
|
||||||
|
map = parseCustomMessage(description)
|
||||||
|
}
|
||||||
|
else if (description?.startsWith('zone status')) {
|
||||||
|
map = parseIasMessage(description)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug "Parse returned $map"
|
||||||
|
def result = map ? createEvent(map) : null
|
||||||
|
|
||||||
|
if (description?.startsWith('enroll request')) {
|
||||||
|
List cmds = enrollResponse()
|
||||||
|
log.debug "enroll response: ${cmds}"
|
||||||
|
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map parseCatchAllMessage(String description) {
|
||||||
|
Map resultMap = [:]
|
||||||
|
def cluster = zigbee.parse(description)
|
||||||
|
if (shouldProcessMessage(cluster)) {
|
||||||
|
switch(cluster.clusterId) {
|
||||||
|
case 0x0001:
|
||||||
|
resultMap = getBatteryResult(cluster.data.last())
|
||||||
|
break
|
||||||
|
|
||||||
|
case 0x0402:
|
||||||
|
log.debug 'TEMP'
|
||||||
|
// temp is last 2 data values. reverse to swap endian
|
||||||
|
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||||
|
def value = getTemperature(temp)
|
||||||
|
resultMap = getTemperatureResult(value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldProcessMessage(cluster) {
|
||||||
|
// 0x0B is default response indicating message got through
|
||||||
|
// 0x07 is bind message
|
||||||
|
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||||
|
cluster.command == 0x0B ||
|
||||||
|
cluster.command == 0x07 ||
|
||||||
|
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||||
|
return !ignoredMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getHumidity(value) {
|
||||||
|
return Math.round(Double.parseDouble(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map parseReportAttributeMessage(String description) {
|
||||||
|
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||||
|
def nameAndValue = param.split(":")
|
||||||
|
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||||
|
}
|
||||||
|
log.debug "Desc Map: $descMap"
|
||||||
|
|
||||||
|
Map resultMap = [:]
|
||||||
|
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||||
|
def value = getTemperature(descMap.value)
|
||||||
|
resultMap = getTemperatureResult(value)
|
||||||
|
}
|
||||||
|
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||||
|
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map parseCustomMessage(String description) {
|
||||||
|
Map resultMap = [:]
|
||||||
|
if (description?.startsWith('temperature: ')) {
|
||||||
|
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
|
||||||
|
resultMap = getTemperatureResult(value)
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map parseIasMessage(String description) {
|
||||||
|
List parsedMsg = description.split(' ')
|
||||||
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
|
}
|
||||||
|
|
||||||
|
def getTemperature(value) {
|
||||||
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
|
if(getTemperatureScale() == "C"){
|
||||||
|
return celsius
|
||||||
|
} else {
|
||||||
|
return celsiusToFahrenheit(celsius) as Integer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map getBatteryResult(rawValue) {
|
||||||
|
log.debug 'Battery'
|
||||||
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
|
def result = [
|
||||||
|
name: 'battery'
|
||||||
|
]
|
||||||
|
|
||||||
|
def volts = rawValue / 10
|
||||||
|
def descriptionText
|
||||||
|
if (rawValue == 0 || rawValue == 255) {}
|
||||||
|
else if (volts > 3.5) {
|
||||||
|
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
def minVolts = 2.1
|
||||||
|
def maxVolts = 3.0
|
||||||
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map getTemperatureResult(value) {
|
||||||
|
log.debug 'TEMP'
|
||||||
|
def linkText = getLinkText(device)
|
||||||
|
if (tempOffset) {
|
||||||
|
def offset = tempOffset as int
|
||||||
|
def v = value as int
|
||||||
|
value = v + offset
|
||||||
|
}
|
||||||
|
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||||
|
return [
|
||||||
|
name: 'temperature',
|
||||||
|
value: value,
|
||||||
|
descriptionText: descriptionText
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map getContactResult(value) {
|
||||||
|
log.debug 'Contact Status'
|
||||||
|
def linkText = getLinkText(device)
|
||||||
|
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
|
||||||
|
return [
|
||||||
|
name: 'contact',
|
||||||
|
value: value,
|
||||||
|
descriptionText: descriptionText
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def refresh() {
|
||||||
|
log.debug "Refreshing Temperature and Battery"
|
||||||
|
def refreshCmds = [
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
||||||
|
]
|
||||||
|
|
||||||
|
return refreshCmds + enrollResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure() {
|
||||||
|
|
||||||
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
|
def configCmds = [
|
||||||
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
|
||||||
|
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||||
|
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||||
|
]
|
||||||
|
return configCmds + refresh() // send refresh cmds as part of config
|
||||||
|
}
|
||||||
|
|
||||||
|
def enrollResponse() {
|
||||||
|
log.debug "Sending enroll response"
|
||||||
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
|
[
|
||||||
|
//Resending the CIE in case the enroll request is sent before CIE is written
|
||||||
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
//Enroll Response
|
||||||
|
"raw 0x500 {01 23 00 00 00}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEndpointId() {
|
||||||
|
new BigInteger(device.endpointId, 16).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private hex(value) {
|
||||||
|
new BigInteger(Math.round(value).toString()).toString(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
private String swapEndianHex(String hex) {
|
||||||
|
reverseArray(hex.decodeHex()).encodeHex()
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] reverseArray(byte[] array) {
|
||||||
|
int i = 0;
|
||||||
|
int j = array.length - 1;
|
||||||
|
byte tmp;
|
||||||
|
while (j > i) {
|
||||||
|
tmp = array[j];
|
||||||
|
array[j] = array[i];
|
||||||
|
array[i] = tmp;
|
||||||
|
j--;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
@@ -403,21 +403,39 @@ def refresh() {
|
|||||||
|
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||||
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
|
refreshCmds = refreshCmds + [
|
||||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
|
||||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||||
as of now.
|
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])
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||||
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||||
|
]
|
||||||
} else {
|
} else {
|
||||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
refreshCmds = refreshCmds + [
|
||||||
|
/* sensitivity - default value (8) */
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
//Common refresh commands
|
//Common refresh commands
|
||||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
refreshCmds = refreshCmds + [
|
||||||
zigbee.readAttribute(0x0001, 0x0020) +
|
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||||
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global read 0xFC02 0x0010",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1","delay 400"
|
||||||
|
]
|
||||||
|
|
||||||
return refreshCmds + enrollResponse()
|
return refreshCmds + enrollResponse()
|
||||||
}
|
}
|
||||||
@@ -425,15 +443,38 @@ def refresh() {
|
|||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||||
|
|
||||||
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting"
|
log.debug "Configuring Reporting"
|
||||||
|
|
||||||
def configCmds = enrollResponse() +
|
def configCmds = [
|
||||||
zigbee.batteryConfig() +
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
zigbee.temperatureConfig() +
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
|
||||||
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||||
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
|
||||||
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||||
|
]
|
||||||
|
|
||||||
return configCmds + refresh()
|
return configCmds + refresh()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ metadata {
|
|||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
|
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,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
|
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x72,0x5A,0x80,0x73,0x86,0x84,0x85,0x59,0x71,0x70,0x7A,0x98" // Vision door/window
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98,0x86,0x72,0x5A,0x85,0x59,0x73,0x80,0x71,0x31,0x70,0x84,0x7A" // Vision Motion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -264,9 +263,5 @@ def retypeBasedOnMSR() {
|
|||||||
log.debug "Changing device type to Door / Window Sensor Plus (SG)"
|
log.debug "Changing device type to Door / Window Sensor Plus (SG)"
|
||||||
setDeviceType("Door / Window Sensor Plus (SG)")
|
setDeviceType("Door / Window Sensor Plus (SG)")
|
||||||
break
|
break
|
||||||
case "0109-2002-0205": // Vision Motion
|
|
||||||
log.debug "Changing device type to Vision Motion Sensor Plus (SG)"
|
|
||||||
setDeviceType("Vision Motion Sensor Plus (SG)")
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,6 @@ metadata {
|
|||||||
capability "Motion Sensor"
|
capability "Motion Sensor"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Battery"
|
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 {
|
simulator {
|
||||||
@@ -132,9 +125,9 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
|||||||
}
|
}
|
||||||
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
||||||
result << response(zwave.batteryV1.batteryGet())
|
result << response(zwave.batteryV1.batteryGet())
|
||||||
} else {
|
result << response("delay 1200")
|
||||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
}
|
}
|
||||||
|
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,333 +0,0 @@
|
|||||||
/**
|
|
||||||
* CCTest
|
|
||||||
*
|
|
||||||
* Copyright 2016 Kuldip
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
|
||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing permissions and limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
definition(
|
|
||||||
name: "CloudCar",
|
|
||||||
namespace: "CloudCar",
|
|
||||||
author: "Kuldip@CloudCar",
|
|
||||||
description: "Connect Car and Home",
|
|
||||||
category: "Convenience",
|
|
||||||
iconUrl: "http://justdrive.net/wp-content/themes/justdrive/assets/img/jd-logo-sans.png",
|
|
||||||
iconX2Url: "http://justdrive.net/wp-content/themes/justdrive/assets/img/jd-logo-sans.png",
|
|
||||||
iconX3Url: "http://justdrive.net/wp-content/themes/justdrive/assets/img/jd-logo-sans.png",
|
|
||||||
oauth: [displayName: "CloudCar", displayLink: "https://CloudCar.com"]
|
|
||||||
)
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
section ("Control your home while driving your car") {
|
|
||||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
|
||||||
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false
|
|
||||||
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors?", multiple: true, required: false
|
|
||||||
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors?", multiple: true, required: false
|
|
||||||
input "temperatureSensors", "capability.temperatureMeasurement", title: "Which Temperature Sensors?", multiple: true, required: false
|
|
||||||
input "accelerationSensors", "capability.accelerationSensor", title: "Which Vibration Sensors?", multiple: true, required: false
|
|
||||||
input "waterSensors", "capability.waterSensor", title: "Which Water Sensors?", multiple: true, required: false
|
|
||||||
input "lightSensors", "capability.illuminanceMeasurement", title: "Which Light Sensors?", multiple: true, required: false
|
|
||||||
input "humiditySensors", "capability.relativeHumidityMeasurement", title: "Which Relative Humidity Sensors?", multiple: true, required: false
|
|
||||||
input "alarms", "capability.alarm", title: "Which Sirens?", multiple: true, required: false
|
|
||||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mappings {
|
|
||||||
path("/scan") {
|
|
||||||
action: [
|
|
||||||
GET: "scanDevices"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/control/:command") {
|
|
||||||
action: [
|
|
||||||
GET: "updateSwitches"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/control/:deviceType/:deviceId/:command") {
|
|
||||||
action: [
|
|
||||||
GET: "updateDevice"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
log.debug "[installed]: state ${state}"
|
|
||||||
initialize ()
|
|
||||||
}
|
|
||||||
|
|
||||||
def uninstalled() {
|
|
||||||
log.debug "[uninstalled]: ${settings}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated () {
|
|
||||||
log.debug "[updated]: ${settings}"
|
|
||||||
unsubscribe()
|
|
||||||
initialize ()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize () {
|
|
||||||
state.clear() // clear previous state - state is loaded from persistant store
|
|
||||||
addSubscription()
|
|
||||||
}
|
|
||||||
|
|
||||||
def addSubscription() {
|
|
||||||
def callbackUrl = 'http://ec2-54-67-21-37.us-west-1.compute.amazonaws.com/notify' //data.callbackUrl
|
|
||||||
log.debug "[addSubscription] state ${state}"
|
|
||||||
log.debug "[addSubscription] settings ${settings}"
|
|
||||||
settings.each {
|
|
||||||
log.debug "[addSubscription] settings ${it}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def currentDeviceIds = settings.collect { k, devices -> devices }.flatten().collect()// { it.id }.unique()
|
|
||||||
log.debug "[addSubscription] currentDeviceIds ${currentDeviceIds}"
|
|
||||||
currentDeviceIds.each {
|
|
||||||
log.debug "[addSubscription] device ids ${it} - ${it.id}"
|
|
||||||
log.debug "Adding switch subscription " + callbackUrl
|
|
||||||
state[it.id] = [callbackUrl: callbackUrl]
|
|
||||||
}
|
|
||||||
subscribe(switches, "switch.on", mydeviceHandler)
|
|
||||||
subscribe(contactSensors, "contact.open", mydeviceHandler)
|
|
||||||
subscribe(motionSensors, "motion.active", mydeviceHandler)
|
|
||||||
log.debug "[addSubscription] COMPLETED ${state}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def mydeviceHandler(evt) {
|
|
||||||
def callbackUrl = 'http://ec2-54-67-21-37.us-west-1.compute.amazonaws.com/notify' //data.callbackUrl
|
|
||||||
//def callbackUrl = 'https://testing.justdrive.cloudcar.com/notify/5c5dca5f-1b98-fff9-f111-8072b41c5fb3'
|
|
||||||
log.debug "[deviceHandler] $evt.displayName"
|
|
||||||
def deviceInfo = state[evt.deviceId]
|
|
||||||
log.debug "[deviceHandler] deviceInfo ${deviceInfo}"
|
|
||||||
if (deviceInfo) {
|
|
||||||
try {
|
|
||||||
httpPostJson(uri: callbackUrl, path: '', body: [deviceId: evt.deviceId, deviceType: evt.name, deviceName: evt.displayName, value: evt.value]) {
|
|
||||||
log.debug "[deviceHandler] Event data successfully posted"
|
|
||||||
}
|
|
||||||
} catch (groovyx.net.http.ResponseParseException e) {
|
|
||||||
log.debug("Error parsing payload ${e}")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.debug "[deviceHandler] No subscribed device found"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def scanDevices() {
|
|
||||||
log.debug "[scandevices] ${type}"
|
|
||||||
[
|
|
||||||
switches: switches.collect{KPdevice(it,"switches")},
|
|
||||||
contactSensors: contacts.collect{KPdevice(it,"contactSensors")},
|
|
||||||
temperatures: temperatures.collect{KPdevice(it,"temperature")},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
private KPdevice(it, type) {
|
|
||||||
def device_state = null
|
|
||||||
log.debug "KPDevice it: ${it}"
|
|
||||||
if (it.currentValue("switch") == "on" ) {
|
|
||||||
device_state = [label:it.label, type:type, id:it.id]
|
|
||||||
for (attribute in it.supportedAttributes) {
|
|
||||||
device_state."${attribute}" = it.currentValue("${attribute}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug "KPdevice: ${device_state}"
|
|
||||||
device_state ? device_state : null
|
|
||||||
}
|
|
||||||
|
|
||||||
def void updateSwitches() {
|
|
||||||
// use the built-in request object to get the command parameter
|
|
||||||
def command = params.command
|
|
||||||
log.debug "updateSwitches: ${params.command}"
|
|
||||||
// all switches have the command
|
|
||||||
// execute the command on all switches
|
|
||||||
// (note we can do this on the array - the command will be invoked on every element
|
|
||||||
|
|
||||||
switch(command) {
|
|
||||||
case "on":
|
|
||||||
switches.on()
|
|
||||||
break
|
|
||||||
case "off":
|
|
||||||
switches.off()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
httpError(400, "$command is not a valid command for all switches specified")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def void updateDevice() {
|
|
||||||
// use the built-in request object to get the command parameter
|
|
||||||
def command = params.command
|
|
||||||
def deviceId = params.deviceId
|
|
||||||
def devices = settings[params.deviceType]
|
|
||||||
|
|
||||||
log.debug "[updateDevice] ${deviceId} - ${command} - ${devices}"
|
|
||||||
|
|
||||||
// all switches have the command
|
|
||||||
// execute the command on all switches
|
|
||||||
// (note we can do this on the array - the command will be invoked on every element
|
|
||||||
def device = devices.find { it.id == deviceId }
|
|
||||||
if (!device) {
|
|
||||||
httpError(404, "Device not found")
|
|
||||||
} else {
|
|
||||||
device."$command"()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def list() {
|
|
||||||
log.debug "[PROD] list, params: ${params}"
|
|
||||||
def type = params.deviceType
|
|
||||||
settings[type]?.collect{deviceItem(it)} ?: []
|
|
||||||
}
|
|
||||||
|
|
||||||
def listStates() {
|
|
||||||
log.debug "[PROD] liststates, params: ${params}"
|
|
||||||
def type = params.deviceType
|
|
||||||
def attributeName = attributeFor(type)
|
|
||||||
settings[type]?.collect{deviceState(it, it.currentState(attributeName))} ?: []
|
|
||||||
}
|
|
||||||
|
|
||||||
def listSubscriptions() {
|
|
||||||
log.debug "[PROD] listSubscription, params: ${params}"
|
|
||||||
state
|
|
||||||
}
|
|
||||||
|
|
||||||
def update() {
|
|
||||||
def type = params.deviceType
|
|
||||||
def data = request.JSON
|
|
||||||
def devices = settings[type]
|
|
||||||
def device = settings[type]?.find { it.id == params.id }
|
|
||||||
def command = data.command
|
|
||||||
|
|
||||||
log.debug "[PROD] update, params: ${params}, request: ${data}, devices: ${devices*.id}"
|
|
||||||
|
|
||||||
if (!device) {
|
|
||||||
httpError(404, "Device not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validateCommand(device, type, command)) {
|
|
||||||
device."$command"()
|
|
||||||
} else {
|
|
||||||
httpError(403, "Access denied. This command is not supported by current capability.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validating the command passed by the user based on capability.
|
|
||||||
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
def validateCommand(device, deviceType, command) {
|
|
||||||
log.debug "[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) {
|
|
||||||
log.debug "[getCapabilityName] ${type}"
|
|
||||||
switch(type) {
|
|
||||||
case "switches":
|
|
||||||
return "Switch"
|
|
||||||
case "alarms":
|
|
||||||
return "Alarm"
|
|
||||||
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) {
|
|
||||||
log.debug "[getDeviceCapabilities] ${deviceCapabilities}"
|
|
||||||
def map = [:]
|
|
||||||
deviceCapabilities.collect {
|
|
||||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
def show() {
|
|
||||||
def type = params.deviceType
|
|
||||||
def devices = settings[type]
|
|
||||||
def device = devices.find { it.id == params.id }
|
|
||||||
|
|
||||||
log.debug "[PROD] show, params: ${params}, devices: ${devices*.id}"
|
|
||||||
if (!device) {
|
|
||||||
httpError(404, "Device not found")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def attributeName = attributeFor(type)
|
|
||||||
def s = device.currentState(attributeName)
|
|
||||||
deviceState(device, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def removeSubscription() {
|
|
||||||
def type = params.deviceType
|
|
||||||
def devices = settings[type]
|
|
||||||
def deviceId = params.id
|
|
||||||
def device = devices.find { it.id == deviceId }
|
|
||||||
|
|
||||||
log.debug "[PROD] removeSubscription, params: ${params}, request: ${data}, device: ${device}"
|
|
||||||
if (device) {
|
|
||||||
log.debug "Removing $device.displayName subscription"
|
|
||||||
state.remove(device.id)
|
|
||||||
unsubscribe(device)
|
|
||||||
}
|
|
||||||
log.info state
|
|
||||||
}
|
|
||||||
|
|
||||||
private deviceItem(it) {
|
|
||||||
log.debug "[deviceItem] ${it}"
|
|
||||||
it ? [id: it.id, label: it.displayName] : null
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private deviceState(device, s) {
|
|
||||||
log.debug "[deviceState] ${device}: ${s}"
|
|
||||||
device && s ? [id: device.id, label: device.displayName, name: s.name, value: s.value, unixTime: s.date.time] : null
|
|
||||||
}
|
|
||||||
|
|
||||||
private attributeFor(type) {
|
|
||||||
log.debug "[attributeFor] ${type}"
|
|
||||||
switch (type) {
|
|
||||||
case "switches":
|
|
||||||
log.debug "[PROD] switch type"
|
|
||||||
return "switch"
|
|
||||||
case "locks":
|
|
||||||
log.debug "[PROD] lock type"
|
|
||||||
return "lock"
|
|
||||||
case "alarms":
|
|
||||||
log.debug "[PROD] alarm type"
|
|
||||||
return "alarm"
|
|
||||||
case "lightSensors":
|
|
||||||
log.debug "[PROD] illuminance type"
|
|
||||||
return "illuminance"
|
|
||||||
default:
|
|
||||||
log.debug "[PROD] other sensor type"
|
|
||||||
return "Sensors"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -337,10 +337,10 @@ def initialize() {
|
|||||||
|
|
||||||
settings.devices.each {
|
settings.devices.each {
|
||||||
def deviceId = it
|
def deviceId = it
|
||||||
def detail = state?.deviceDetail[deviceId]
|
def detail = state.deviceDetail[deviceId]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch(detail?.type) {
|
switch(detail.type) {
|
||||||
case 'NAMain':
|
case 'NAMain':
|
||||||
log.debug "Base station"
|
log.debug "Base station"
|
||||||
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
|
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
|
||||||
@@ -487,12 +487,12 @@ def poll() {
|
|||||||
log.debug "State: ${state.deviceState}"
|
log.debug "State: ${state.deviceState}"
|
||||||
|
|
||||||
settings.devices.each { deviceId ->
|
settings.devices.each { deviceId ->
|
||||||
def detail = state?.deviceDetail[deviceId]
|
def detail = state.deviceDetail[deviceId]
|
||||||
def data = state?.deviceState[deviceId]
|
def data = state.deviceState[deviceId]
|
||||||
def child = children?.find { it.deviceNetworkId == deviceId }
|
def child = children.find { it.deviceNetworkId == deviceId }
|
||||||
|
|
||||||
log.debug "Update: $child";
|
log.debug "Update: $child";
|
||||||
switch(detail?.type) {
|
switch(detail.type) {
|
||||||
case 'NAMain':
|
case 'NAMain':
|
||||||
log.debug "Updating NAMain $data"
|
log.debug "Updating NAMain $data"
|
||||||
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
|
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ preferences {
|
|||||||
page(name: "completionPage")
|
page(name: "completionPage")
|
||||||
page(name: "numbersPage")
|
page(name: "numbersPage")
|
||||||
page(name: "controllerExplanationPage")
|
page(name: "controllerExplanationPage")
|
||||||
page(name: "unsupportedDevicesPage")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def rootPage() {
|
def rootPage() {
|
||||||
@@ -48,9 +47,6 @@ def rootPage() {
|
|||||||
section("What to dim") {
|
section("What to dim") {
|
||||||
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
|
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
|
||||||
if (dimmers) {
|
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")
|
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,31 +71,6 @@ 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() {
|
def controllerExplanationPage() {
|
||||||
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
|
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
|
||||||
|
|
||||||
@@ -557,16 +528,14 @@ def updateDimmers(percentComplete) {
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
def shouldChangeColors = (colorize && colorize != "false")
|
def shouldChangeColors = (colorize && colorize != "false")
|
||||||
|
def canChangeColors = hasSetColorCommand(dimmer)
|
||||||
|
|
||||||
if (shouldChangeColors && hasSetColorCommand(dimmer)) {
|
log.debug "Setting ${deviceLabel(dimmer)} to ${nextLevel}"
|
||||||
def hue = getHue(dimmer, nextLevel)
|
|
||||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel} and hue to ${hue}"
|
if (shouldChangeColors && canChangeColors) {
|
||||||
dimmer.setColor([hue: hue, saturation: 100, level: nextLevel])
|
dimmer.setColor([hue: getHue(dimmer, nextLevel), saturation: 100, level: nextLevel])
|
||||||
} else if (hasSetLevelCommand(dimmer)) {
|
|
||||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel}"
|
|
||||||
dimmer.setLevel(nextLevel)
|
|
||||||
} else {
|
} else {
|
||||||
log.warn "${deviceLabel(dimmer)} does not have setColor or setLevel commands."
|
dimmer.setLevel(nextLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -848,21 +817,24 @@ private getRedHue(level) {
|
|||||||
if (level >= 96) return 17
|
if (level >= 96) return 17
|
||||||
}
|
}
|
||||||
|
|
||||||
private dimmersContainUnsupportedDevices() {
|
|
||||||
def found = dimmers.find { hasSetLevelCommand(it) == false }
|
|
||||||
return found != null
|
|
||||||
}
|
|
||||||
|
|
||||||
private hasSetLevelCommand(device) {
|
private hasSetLevelCommand(device) {
|
||||||
return hasCommand(device, "setLevel")
|
def isDimmer = false
|
||||||
|
device.supportedCommands.each {
|
||||||
|
if (it.name.contains("setLevel")) {
|
||||||
|
isDimmer = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isDimmer
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasSetColorCommand(device) {
|
private hasSetColorCommand(device) {
|
||||||
return hasCommand(device, "setColor")
|
def hasColor = false
|
||||||
}
|
device.supportedCommands.each {
|
||||||
|
if (it.name.contains("setColor")) {
|
||||||
private hasCommand(device, String command) {
|
hasColor = true
|
||||||
return (device.supportedCommands.find { it.name == command } != null)
|
}
|
||||||
|
}
|
||||||
|
return hasColor
|
||||||
}
|
}
|
||||||
|
|
||||||
private dimmersWithSetColorCommand() {
|
private dimmersWithSetColorCommand() {
|
||||||
|
|||||||
@@ -658,73 +658,29 @@ def updateDevice() {
|
|||||||
def data = request.JSON
|
def data = request.JSON
|
||||||
def command = data.command
|
def command = data.command
|
||||||
def arguments = data.arguments
|
def arguments = data.arguments
|
||||||
|
|
||||||
log.debug "updateDevice, params: ${params}, request: ${data}"
|
log.debug "updateDevice, params: ${params}, request: ${data}"
|
||||||
if (!command) {
|
if (!command) {
|
||||||
render status: 400, data: '{"msg": "command is required"}'
|
render status: 400, data: '{"msg": "command is required"}'
|
||||||
} else {
|
} else {
|
||||||
def device = allDevices.find { it.id == params.id }
|
def device = allDevices.find { it.id == params.id }
|
||||||
if (device) {
|
if (device) {
|
||||||
if (validateCommand(device, command)) {
|
if (device.hasCommand("$command")) {
|
||||||
if (arguments) {
|
if (arguments) {
|
||||||
device."$command"(*arguments)
|
device."$command"(*arguments)
|
||||||
} else {
|
} else {
|
||||||
device."$command"()
|
device."$command"()
|
||||||
}
|
}
|
||||||
render status: 204, data: "{}"
|
render status: 204, data: "{}"
|
||||||
} else {
|
} else {
|
||||||
render status: 403, data: '{"msg": "Access denied. This command is not supported by current capability."}'
|
render status: 404, data: '{"msg": "Command not supported by this Device"}'
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
render status: 404, data: '{"msg": "Device not found"}'
|
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] ? 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() {
|
def listSubscriptions() {
|
||||||
log.debug "listSubscriptions()"
|
log.debug "listSubscriptions()"
|
||||||
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
|
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
|
||||||
@@ -824,51 +780,17 @@ def deviceHandler(evt) {
|
|||||||
|
|
||||||
def sendToHarmony(evt, String callbackUrl) {
|
def sendToHarmony(evt, String callbackUrl) {
|
||||||
def callback = new URI(callbackUrl)
|
def callback = new URI(callbackUrl)
|
||||||
if(isIP(callback.host)){
|
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
||||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
||||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
sendHubCommand(new physicalgraph.device.HubAction(
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(
|
method: "POST",
|
||||||
method: "POST",
|
path: path,
|
||||||
path: path,
|
headers: [
|
||||||
headers: [
|
"Host": host,
|
||||||
"Host": host,
|
"Content-Type": "application/json"
|
||||||
"Content-Type": "application/json"
|
],
|
||||||
],
|
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isIP(String str) {
|
|
||||||
try {
|
|
||||||
String[] parts = str.split("\\.");
|
|
||||||
if (parts.length != 4) return false;
|
|
||||||
for (int i = 0; i < 4; ++i) {
|
|
||||||
int p
|
|
||||||
try {
|
|
||||||
p = Integer.parseInt(parts[i]);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (p > 255 || p < 0) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def listHubs() {
|
def listHubs() {
|
||||||
|
|||||||
@@ -92,86 +92,21 @@ void updateLock() {
|
|||||||
|
|
||||||
private void updateAll(devices) {
|
private void updateAll(devices) {
|
||||||
def command = request.JSON?.command
|
def command = request.JSON?.command
|
||||||
def type = params.param1
|
if (command) {
|
||||||
if (!devices) {
|
devices."$command"()
|
||||||
httpError(404, "Devices not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command){
|
|
||||||
devices.each { device ->
|
|
||||||
executeCommand(device, type, command)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update(devices) {
|
private void update(devices) {
|
||||||
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
||||||
def command = request.JSON?.command
|
def command = request.JSON?.command
|
||||||
def type = params.param1
|
|
||||||
def device = devices?.find { it.id == params.id }
|
|
||||||
|
|
||||||
if (!device) {
|
|
||||||
httpError(404, "Device not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command) {
|
if (command) {
|
||||||
executeCommand(device, type, command)
|
def device = devices.find { it.id == params.id }
|
||||||
}
|
if (!device) {
|
||||||
}
|
httpError(404, "Device not found")
|
||||||
|
} else {
|
||||||
/**
|
device."$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.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user