mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-08 05:31:56 +00:00
MSA-1875: Originally developed by Don Caruanna with minor changes from Sensative.
This is intended to be the official/default device handler for Strips by Sensative. The preference defaults are set to the device defaults. (If you are looking for accurate values in the IDE, you have to hit "Done" in preferences once, even if you don't change any settings.) Preferences with an asterisk have to wait for the next wakeup, automatic or manual. The pending icon will display if there are changes pending and say "Synced" otherwise. If you revert your preferences to values that match the device before the next wakeup, it will go back to "Synced". Only changed preferences are pushed to the device. The actual wakeup by the device is approximately 90% of the wakeupinterval setting. So the observed wakeup interval will be a little less than the chosen one. Also, the wakeupinterval has a minimum of 1800 and a maximum of 86400. A tamper notification is sent in 2 cases: Putting a magnet on the round edge when there is a magnet on the square one triggers tamper During a Manual Wake Up, a tamper is sent before the Node Info. The tamper event time can be cleared by just tapping on the time itself. Tamper events are reported so they can be seen historically in the GUI.
This commit is contained in:
@@ -0,0 +1,438 @@
|
||||
/**
|
||||
* Strips by Sensative
|
||||
* Device Handler by Don Caruana
|
||||
*
|
||||
* Date: 2017-2-19
|
||||
* Supported Command Classes per device specs
|
||||
*
|
||||
* Association v2
|
||||
* Association Group Information
|
||||
* Battery
|
||||
* Binary Sensor
|
||||
* Configuration
|
||||
* Device Reset Local
|
||||
* Manufacturer Specific
|
||||
* Notification v4
|
||||
* Powerlevel
|
||||
* Version v2
|
||||
* Wake Up v2
|
||||
* ZWavePlus Info v2
|
||||
*
|
||||
* Parm Size Description Value
|
||||
* 1 1 Type of report to send 1 (Default)-Notification Report, 0-Binary Sensor report, 2-Basic report
|
||||
* 2 1 LED Indication 1 (Default)-On for event (ex. door opened), 0-Off
|
||||
*
|
||||
* This device handler will just override the smartthings default wakeup interval of 4 hours and set to 24 hours (manufacturer default)
|
||||
* and check the battery once a day (no sooner than every 23 hours)
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Strips by Sensative", namespace: "doncaruana/Sensative", author: "Don Caruana, Sensative") {
|
||||
|
||||
capability "Contact Sensor"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Sensor"
|
||||
capability "Refresh"
|
||||
attribute "needUpdate", "string"
|
||||
attribute "tamper" , "string"
|
||||
|
||||
fingerprint mfr:"019A", prod:"0003", model:"0003", deviceJoinName:"Strips by Sensative"
|
||||
fingerprint deviceId:"0x0701", inClusters: "0x5E,0x86,0x72,0x30,0x70,0x71,0x5A,0x85,0x59,0x80,0x84,0x73"
|
||||
fingerprint cc: "0x5E,0x86,0x72,0x30,0x70,0x71,0x5A,0x85,0x59,0x80,0x84,0x73", mfr:"019A", prod:"0003", model:"0003", deviceJoinName:"Strips by Sensative"
|
||||
}
|
||||
|
||||
preferences {
|
||||
section ("configuration settings") {
|
||||
input(
|
||||
title : "Changes in Settings marked with * will not take effect until the next device Wake Up."
|
||||
,description : null
|
||||
,type : "paragraph")
|
||||
input "sendType", "enum",
|
||||
title: "* Reporting Type",
|
||||
description: "Notification Report",
|
||||
options:["binary": "Sensor Binary Report", "notification": "Notification Report", "basic": "Basic Report"],
|
||||
defaultValue: "notification",
|
||||
displayDuringSetup: false
|
||||
input "wakeupInterval","enum",
|
||||
title: "* Device Wake Up Interval",
|
||||
description: "24 hours",
|
||||
defaultValue: "86400",
|
||||
required: false,
|
||||
displayDuringSetup: false,
|
||||
options: buildInterval()
|
||||
input "led", "bool",
|
||||
title: "* LED On",
|
||||
defaultValue: true,
|
||||
displayDuringSetup: false
|
||||
input "ignoreWakeup", "bool",
|
||||
title: "Ignore Manual Wake Up",
|
||||
defaultValue: false,
|
||||
displayDuringSetup: false
|
||||
input "invertOutput", "bool",
|
||||
title: "Invert Open/Close Reporting",
|
||||
defaultValue: false,
|
||||
displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"contact", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||
}
|
||||
}
|
||||
standardTile("tamper", "device.tamper", inactiveLabel: false, width: 4, height: 3) {
|
||||
state "tamper", label:'${currentValue}', action:"refresh.refresh", backgroundColor:"#FFCC7B"
|
||||
}
|
||||
standardTile("configure", "device.needUpdate", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "NO" , label:'Synced', backgroundColor:"#B1D57D"
|
||||
state "YES", label:'Pending changes', backgroundColor:"#FFCC7B"
|
||||
}
|
||||
standardTile("battery", "device.battery", inactiveLabel: false, width: 2, height: 1) {
|
||||
state "battery", label:'Battery: ${currentValue}%'
|
||||
}
|
||||
|
||||
main (["contact"])
|
||||
details(["contact","battery","tamper","configure"])
|
||||
}
|
||||
|
||||
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 "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() {
|
||||
def commands = []
|
||||
state.lastupdate = now()
|
||||
sendEvent(name: "tamper", value: "No Tamper", displayed: false)
|
||||
log.debug "Listing all device parameters and defaults since this is a new inclusion"
|
||||
commands << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
|
||||
commands << zwave.versionV1.versionGet().format()
|
||||
commands << zwave.batteryV1.batteryGet().format()
|
||||
commands << zwave.configurationV1.configurationGet(parameterNumber: 1).format()
|
||||
commands << zwave.configurationV1.configurationGet(parameterNumber: 2).format()
|
||||
commands << zwave.wakeUpV2.wakeUpIntervalSet(seconds: 86400, nodeid:zwaveHubNodeId).format()
|
||||
commands << zwave.wakeUpV2.wakeUpIntervalGet().format()
|
||||
commands << zwave.wakeUpV2.wakeUpNoMoreInformation().format()
|
||||
delayBetween(commands, 1500)
|
||||
}
|
||||
|
||||
private getCommandClassVersions() {
|
||||
[
|
||||
0x71: 3, // Notification
|
||||
0x5E: 2, // ZwaveplusInfo
|
||||
0x59: 1, // AssociationGrpInfo
|
||||
0x85: 2, // Association
|
||||
0x80: 1, // Battery
|
||||
0x70: 1, // Configuration
|
||||
0x5A: 1, // DeviceResetLocally
|
||||
0x72: 1, // ManufacturerSpecific
|
||||
0x73: 1, // Powerlevel
|
||||
0x84: 2, // WakeUp
|
||||
0x86: 1, // Version
|
||||
0x30: 1, // Binary Sensor
|
||||
]
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when Done button is pushed on Preference Pane
|
||||
*/
|
||||
def updated()
|
||||
{
|
||||
if(now() - state.lastupdate > 3000){
|
||||
def isUpdateNeeded = "NO"
|
||||
if(wakeupInterval != null && state.wakeupInterval != wakeupInterval) {isUpdateNeeded = "YES"}
|
||||
if (sendType != null && sendType != state.sendType) {isUpdateNeeded = "YES"}
|
||||
if (led != null && led != state.led) {isUpdateNeeded = "YES"}
|
||||
state.lastupdate = now()
|
||||
sendEvent(name:"needUpdate", value: isUpdateNeeded, displayed:false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only device parameter changes require a state change
|
||||
*/
|
||||
def update_settings()
|
||||
{
|
||||
def cmds = []
|
||||
def isUpdateNeeded = "NO"
|
||||
if (state.wakeupInterval != wakeupInterval){
|
||||
cmds << zwave.wakeUpV2.wakeUpIntervalSet(seconds: wakeupInterval.toInteger(), nodeid:zwaveHubNodeId).format()
|
||||
cmds << "delay 1000"
|
||||
cmds << zwave.wakeUpV2.wakeUpIntervalGet().format()
|
||||
}
|
||||
if (sendType != state.sendType){
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1, configurationValue: [sendType == "binary" ? 0 : sendType == "basic" ? 2 : 1]).format()
|
||||
cmds << "delay 1000"
|
||||
cmds << zwave.configurationV1.configurationGet(parameterNumber: 1).format()
|
||||
}
|
||||
if (led != state.led){
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, configurationValue: [led == true ? 1 : 0]).format()
|
||||
cmds << "delay 1000"
|
||||
cmds << zwave.configurationV1.configurationGet(parameterNumber: 2).format()
|
||||
}
|
||||
cmds << "delay 1000"
|
||||
sendEvent(name:"needUpdate", value: isUpdateNeeded, displayed:false, isStateChange: true)
|
||||
return cmds
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
|
||||
def name = ""
|
||||
def value = ""
|
||||
def tmpParm = cmd.parameterNumber
|
||||
|
||||
def reportValue = cmd.configurationValue[0]
|
||||
switch (cmd.parameterNumber) {
|
||||
case 1:
|
||||
name = "sendType"
|
||||
switch (reportValue) {
|
||||
case 0:
|
||||
value = "binary"
|
||||
break
|
||||
case 1:
|
||||
value = "notification"
|
||||
break
|
||||
case 2:
|
||||
value = "basic"
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
state.sendType = value
|
||||
log.debug "sendType = $value"
|
||||
break
|
||||
case 2:
|
||||
name = "led"
|
||||
value = reportValue
|
||||
log.debug "led = $value"
|
||||
state.led = reportValue == 1 ? true : false
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
sendEvent(name: name, value: value, displayed: true)
|
||||
}
|
||||
|
||||
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 the invertOutput parameter is set, logically invert the output value
|
||||
def flip = 0
|
||||
if (state.sendType == "notification"){
|
||||
flip = value ^ 0x1}
|
||||
else{
|
||||
flip = value ^ 0xFF}
|
||||
if (invertOutput) {value = flip}
|
||||
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.sensorbinaryv1.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.manufacturerSpecificV1.manufacturerSpecificGet()))
|
||||
}
|
||||
} else if (cmd.event == 0x04) {
|
||||
def timeString1 = new Date().format("MMM d", location.timeZone)
|
||||
def timeString2 = new Date().format("hh:mm:ss", location.timeZone)
|
||||
result << createEvent(name: "tamper", value: "Tampered\n${timeString1}\n${timeString2}", descriptionText: "$device.displayName was tampered with at ${timeString1} ${timeString2}", isStateChange: true)
|
||||
if (!ignoreWakeup) {
|
||||
sendEvent(name:"WakeUp", value: "Manual Wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: true)
|
||||
result << doWakeup()
|
||||
}
|
||||
} 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: "Auto Wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: true)
|
||||
def cmds = []
|
||||
|
||||
if (!device.currentState("ManufacturerCode")) {
|
||||
cmds << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
|
||||
cmds << "delay 2000"
|
||||
}
|
||||
if (!state.lastbat || now() - state.lastbat > 23*60*60*1000) { //check no sooner than once every 23 hours (once a day)
|
||||
log.debug "checking battery"
|
||||
event.descriptionText += ", requesting battery"
|
||||
cmds << zwave.batteryV1.batteryGet().format()
|
||||
cmds << "delay 2000"
|
||||
} else {
|
||||
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
|
||||
}
|
||||
if (device.currentValue("needUpdate") == "YES") { cmds += update_settings() }
|
||||
cmds << zwave.wakeUpV2.wakeUpNoMoreInformation().format()
|
||||
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)
|
||||
map.isStateChange = true
|
||||
state.lastbat = now()
|
||||
return [event]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv1.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"
|
||||
|
||||
updateDataValue("Manufacturer", cmd.manufacturerName)
|
||||
updateDataValue("Manufacturer ID", manufacturerCode)
|
||||
updateDataValue("Product Type", productTypeCode)
|
||||
updateDataValue("Product Code", productCode)
|
||||
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)
|
||||
}
|
||||
|
||||
def buildInterval() {
|
||||
def intervalList = []
|
||||
intervalList << [ "0" : "Disabled" ]
|
||||
intervalList << [ "1800" : "30 minutes" ]
|
||||
intervalList << [ "3600" : "1 hour" ]
|
||||
intervalList << [ "7200" : "2 hours" ]
|
||||
intervalList << [ "10800" : "3 hours" ]
|
||||
intervalList << [ "14400" : "4 hours" ]
|
||||
intervalList << [ "18000" : "5 hours" ]
|
||||
intervalList << [ "21600" : "6 hours" ]
|
||||
intervalList << [ "36000" : "10 hours" ]
|
||||
intervalList << [ "43200" : "12 hours" ]
|
||||
intervalList << [ "86400" : "24 hours" ]
|
||||
}
|
||||
|
||||
def doWakeup() {
|
||||
def cmds = []
|
||||
cmds << "delay 2000"
|
||||
if (device.currentValue("needUpdate") == "YES") { cmds += update_settings() }
|
||||
cmds << zwave.wakeUpV2.wakeUpNoMoreInformation().format()
|
||||
return response(cmds)
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
sendEvent(name: "tamper", value: "No Tamper", displayed: false)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
||||
def appversion = String.format("%02d.%02d", cmd.applicationVersion, cmd.applicationSubVersion)
|
||||
def zprotoversion = String.format("%d.%02d", cmd.zWaveProtocolVersion, cmd.zWaveProtocolSubVersion)
|
||||
updateDataValue("zWave Library", cmd.zWaveLibraryType.toString())
|
||||
updateDataValue("Firmware", appversion)
|
||||
updateDataValue("zWave Version", zprotoversion)
|
||||
sendEvent(name: "Firmware", value: appversion, displayed: true)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpIntervalReport cmd) {
|
||||
state.wakeupInterval = cmd.seconds.toString()
|
||||
sendEvent(name: "wakeupInterval", value: state.wakeupInterval, displayed: true)
|
||||
}
|
||||
Reference in New Issue
Block a user