Compare commits

..

1 Commits

9 changed files with 318 additions and 813 deletions

View File

@@ -15,13 +15,12 @@
*/ */
metadata { metadata {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery" capability "Battery"
capability "Configuration" capability "Configuration"
capability "Contact Sensor" capability "Contact Sensor"
capability "Refresh" capability "Refresh"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Health Check"
command "enrollResponse" command "enrollResponse"
@@ -274,8 +273,7 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [ def configCmds = [

View File

@@ -28,7 +28,7 @@ metadata {
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98" fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98"
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,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
} }
// simulator metadata // simulator metadata
@@ -81,22 +81,22 @@ def updated() {
def cmds = [] def cmds = []
if (!state.MSR) { if (!state.MSR) {
cmds = [ cmds = [
command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()), zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
"delay 1200", "delay 1200",
zwave.wakeUpV1.wakeUpNoMoreInformation() zwave.wakeUpV1.wakeUpNoMoreInformation().format()
] ]
} else if (!state.lastbat) { } else if (!state.lastbat) {
cmds = [] cmds = []
} else { } else {
cmds = [zwave.wakeUpV1.wakeUpNoMoreInformation()] cmds = [zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
} }
response(cmds) response(cmds)
} }
def configure() { def configure() {
commands([ delayBetween([
zwave.manufacturerSpecificV2.manufacturerSpecificGet(), zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
zwave.batteryV1.batteryGet() batteryGetCommand()
], 6000) ], 6000)
} }
@@ -147,11 +147,12 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
result << sensorValueEvent(1) result << sensorValueEvent(1)
} else if (cmd.event == 0x03) { } else if (cmd.event == 0x03) {
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true) result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet())) result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
} else if (cmd.event == 0x05 || cmd.event == 0x06) { } else if (cmd.event == 0x05 || cmd.event == 0x06) {
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true) result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
} else if (cmd.event == 0x07) { } else if (cmd.event == 0x07) {
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet())) if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion") result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion")
} }
} else if (cmd.notificationType) { } else if (cmd.notificationType) {
@@ -169,13 +170,14 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false) def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
def cmds = [] def cmds = []
if (!state.MSR) { if (!state.MSR) {
cmds << command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) cmds << zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId).format()
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()
cmds << "delay 1200" cmds << "delay 1200"
} }
if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) { if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) {
cmds << command(zwave.batteryV1.batteryGet()) cmds << batteryGetCommand()
} else { } else {
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation() cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
} }
[event, response(cmds)] [event, response(cmds)]
} }
@@ -210,7 +212,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
if (msr == "0086-0102-0059") { if (msr == "0086-0102-0059") {
result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format()) result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format())
} else { } else {
result << response(command(zwave.batteryV1.batteryGet())) result << response(batteryGetCommand())
} }
} }
@@ -218,7 +220,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
} }
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1]) def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1])
// log.debug "encapsulated: $encapsulatedCommand" // log.debug "encapsulated: $encapsulatedCommand"
if (encapsulatedCommand) { if (encapsulatedCommand) {
state.sec = 1 state.sec = 1
@@ -230,16 +232,12 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
createEvent(descriptionText: "$device.displayName: $cmd", displayed: false) createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)
} }
private command(physicalgraph.zwave.Command cmd) { def batteryGetCommand() {
if (state.sec == 1) { def cmd = zwave.batteryV1.batteryGet()
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() if (state.sec) {
} else { cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)
cmd.format()
} }
} cmd.format()
private commands(commands, delay=200) {
delayBetween(commands.collect{ command(it) }, delay)
} }
def retypeBasedOnMSR() { def retypeBasedOnMSR() {
@@ -262,12 +260,8 @@ def retypeBasedOnMSR() {
setDeviceType("3-in-1 Multisensor Plus (SG)") setDeviceType("3-in-1 Multisensor Plus (SG)")
break break
case "0109-2001-0106": // Vision door/window case "0109-2001-0106": // Vision door/window
log.debug "Changing device type to Z-Wave Plus Door/Window Sensor" log.debug "Changing device type to Door / Window Sensor Plus (SG)"
setDeviceType("Z-Wave Plus Door/Window Sensor") setDeviceType("Door / Window Sensor Plus (SG)")
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 break
} }
} }

View File

@@ -1,270 +0,0 @@
/**
* 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)
}

View File

@@ -1,327 +0,0 @@
/**
* 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)
}

View File

@@ -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())

View File

@@ -0,0 +1,253 @@
/**
* Gideon
*
* Copyright 2016 Nicola Russo
*
* 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: "Gideon",
namespace: "gideon.api",
author: "Braindrain Solutions",
description: "Gideon AI Smart app allows you to connect and control all of your SmartThings devices through the Gideon AI app, making your SmartThings devices even smarter.",
category: "Family",
iconUrl: "http://s33.postimg.org/t77u7y7v3/logo.png",
iconX2Url: "http://s33.postimg.org/t77u7y7v3/logo.png",
iconX3Url: "http://s33.postimg.org/t77u7y7v3/logo.png",
oauth: [displayName: "Gideon AI API", displayLink: "gideon.ai"])
preferences {
section("Control these switches...") {
input "switches", "capability.switch", multiple:true
}
section("Control these motion sensors...") {
input "motions", "capability.motionSensor", multiple:true
}
section("Control these presence sensors...") {
input "presence_sensors", "capability.presenceSensor", multiple:true
}
section("Control these outlets...") {
input "outlets", "capability.switch", multiple:true
}
section("Control these locks...") {
input "locks", "capability.lock", multiple:true
}
section("Control these locks...") {
input "temperature_sensors", "capability.temperatureMeasurement"
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// TODO: subscribe to attributes, devices, locations, etc.
subscribe(outlet, "energy", outletHandler)
subscribe(outlet, "switch", outletHandler)
}
// TODO: implement event handlers
def outletHandler(evt) {
log.debug "$outlet.currentEnergy"
//TODO call G API
}
private device(it, type) {
it ? [id: it.id, label: it.label, type: type] : null
}
//API Mapping
mappings {
path("/getalldevices") {
action: [
GET: "getAllDevices"
]
}
path("/doorlocks/:id/:command") {
action: [
GET: "updateDoorLock"
]
}
path("/doorlocks/:id") {
action: [
GET: "getDoorLockStatus"
]
}
path("/tempsensors/:id") {
action: [
GET: "getTempSensorsStatus"
]
}
path("/presences/:id") {
action: [
GET: "getPresenceStatus"
]
}
path("/motions/:id") {
action: [
GET: "getMotionStatus"
]
}
path("/outlets/:id") {
action: [
GET: "getOutletStatus"
]
}
path("/outlets/:id/:command") {
action: [
GET: "updateOutlet"
]
}
path("/switches/:command") {
action: [
PUT: "updateSwitch"
]
}
}
//API Methods
def getAllDevices() {
def locks_list = locks.collect{device(it,"Lock")}
def presences_list = presence_sensors.collect{device(it,"Presence")}
def motions_list = motions.collect{device(it,"Motion")}
def outlets_list = outlets.collect{device(it,"Outlet")}
def switches_list = switches.collect{device(it,"Switch")}
def temp_list = temperature_sensors.collect{device(it,"Temperature")}
return [Locks: locks_list, Presences: presences_list, Motions: motions_list, Outlets: outlets_list, Switches: switches_list, Temperatures: temp_list]
}
//LOCKS
def getDoorLockStatus() {
def device = locks.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('lock')]
}
}
def updateDoorLock() {
def command = params.command
def device = locks.find { it.id == params.id }
if (command){
if (!device) {
httpError(404, "Device not found")
} else {
if(command == "toggle")
{
if(device.currentValue('lock') == "locked")
device.unlock();
else
device.lock();
return [Device_id: params.id, result_action: "200"]
}
}
}
}
//PRESENCE
def getPresenceStatus() {
def device = presence_sensors.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('presence')]
}
}
//MOTION
def getMotionStatus() {
def device = motions.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('motion')]
}
}
//OUTLET
def getOutletStatus() {
def device = outlets.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentSwitch, Current_watt: device.currentValue("energy")]
}
}
def updateOutlet() {
def command = params.command
def device = outlets.find { it.id == params.id }
if (command){
if (!device) {
httpError(404, "Device not found")
} else {
if(command == "toggle")
{
if(device.currentSwitch == "on")
device.off();
else
device.on();
return [Device_id: params.id, result_action: "200"]
}
}
}
}
//SWITCH
def updateSwitch() {
def command = params.command
def device = switches.find { it.id == params.id }
if (command){
if (!device) {
httpError(404, "Device not found")
} else {
if(command == "toggle")
{
if(device.currentSwitch == "on")
device.off();
else
device.on();
return [Device_id: params.id, result_action: "200"]
}
}
}
}
//TEMPERATURE
def getTempSensorsStatus() {
def device = temperature_sensors.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('temperature')]
}
}

View File

@@ -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() {

View File

@@ -92,89 +92,24 @@ 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.")
}
}
private show(devices, name) { private show(devices, name) {
def device = devices.find { it.id == params.id } def device = devices.find { it.id == params.id }
if (!device) { if (!device) {