I am using the Aeon v5 siren and and “smartthings : Z-Wave Switch with Flash” device type for flash of switch and “krlaframboise : Aeon Labs Multifunction Siren” that gives me the number of beeps.
This is the switch DTH code:
indent preformatted text by 4 spaces
/**
- Copyright 2015 SmartThings
- Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except
- in compliance with the License. You may obtain a copy of the License at:
-
http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
- on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
- for the specific language governing permissions and limitations under the License.
*/
metadata {
definition (name: “Z-Wave Switch with Flash”, namespace: “smartthings”, author: “SmartThings”) {
capability "Actuator"
capability "Indicator"
capability "Switch"
capability "Polling"
capability "Refresh"
capability “Sensor”
command "flash"
fingerprint inClusters: "0x25"
}
// simulator metadata
simulator {
status "on": "command: 2003, payload: FF"
status "off": "command: 2003, payload: 00"
// reply messages
reply "2001FF,delay 100,2502": "command: 2503, payload: FF"
reply "200100,delay 100,2502": "command: 2503, payload: 00"
}
// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
}
}
standardTile("indicator", "device.indicatorStatus", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "when off", action:"indicator.indicatorWhenOn", icon:"st.indicators.lit-when-off"
state "when on", action:"indicator.indicatorNever", icon:"st.indicators.lit-when-on"
state "never", action:"indicator.indicatorWhenOff", icon:"st.indicators.never-lit"
}
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch","refresh","indicator"])
}
}
def parse(String description) {
def result = null
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
if (cmd) {
result = createEvent(zwaveEvent(cmd))
}
if (result?.name == ‘hail’ && hubFirmwareLessThan(“000.011.00602”)) {
result = [result, response(zwave.basicV1.basicGet())]
log.debug “Was hailed: requesting state update”
} else {
log.debug “Parse returned ${result?.descriptionText}”
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
[name: “switch”, value: cmd.value ? “on” : “off”, type: “physical”]
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
[name: “switch”, value: cmd.value ? “on” : “off”, type: “physical”]
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
[name: “switch”, value: cmd.value ? “on” : “off”, type: “digital”]
}
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
def value = "when off"
if (cmd.configurationValue[0] == 1) {value = “when on”}
if (cmd.configurationValue[0] == 2) {value = “never”}
[name: “indicatorStatus”, value: value, display: false]
}
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
[name: “hail”, value: “hail”, descriptionText: “Switch button was pressed”, displayed: false]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
if (state.manufacturer != cmd.manufacturerName) {
updateDataValue(“manufacturer”, cmd.manufacturerName)
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren’t interested in
[:]
}
def on() {
delayBetween([
zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.switchBinaryV1.switchBinaryGet().format()
])
}
def off() {
delayBetween([
zwave.basicV1.basicSet(value: 0x00).format(),
zwave.switchBinaryV1.switchBinaryGet().format()
])
}
def flash() {
delayBetween ([
delayBetween ([ zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.basicV1.basicSet(value: 0x00).format()], 1000),
delayBetween ([ zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.basicV1.basicSet(value: 0x00).format()], 1000),
delayBetween ([ zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.basicV1.basicSet(value: 0x00).format()], 1000),
delayBetween ([ zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.basicV1.basicSet(value: 0x00).format()], 1000),
zwave.switchBinaryV1.switchBinaryGet().format()], 2000)
}
def poll() {
delayBetween([
zwave.switchBinaryV1.switchBinaryGet().format(),
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
])
}
def refresh() {
delayBetween([
zwave.switchBinaryV1.switchBinaryGet().format(),
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
])
}
def indicatorWhenOn() {
sendEvent(name: “indicatorStatus”, value: “when on”, display: false)
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
}
def indicatorWhenOff() {
sendEvent(name: “indicatorStatus”, value: “when off”, display: false)
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
}
def indicatorNever() {
sendEvent(name: “indicatorStatus”, value: “never”, display: false)
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
}
def invertSwitch(invert=true) {
if (invert) {
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
}
else {
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
}
}
This is the Siren code:
/**
-
Aeoc Labs Multifunction Siren v 1.4
-
Capabilities:
-
Switch, Alarm, Tone, Music Player
-
Author:
-
Kevin LaFramboise (krlaframboise)
-
Changelog:
-
1.4 (03/05/2016)
-
- Enhanced Logging
-
- Fixed bug with beep schedule cancellation.
-
1.3 (03/03/2016)
-
- Added startBeepDelayedAlarm command.
-
- Fixed validation logging bug.
-
1.2 (02/29/2016)
-
- Fixed IOS UI issue with beep buttons.
-
- Added Music Player capability.
-
- Added TTS command support so that it can be used
-
with SHM, Notify with Sound, and Rule Machine
-
- Added alarm delay feature.
-
1.1 (02/28/2016)
-
- Logging Enhancements.
-
1.0 (02/28/2016)
-
- Initial Release
-
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: “Aeon Labs Multifunction Siren”, namespace: “krlaframboise”, author: “Kevin LaFramboise”) {
capability "Actuator"
capability "Switch"
capability "Alarm"
capability "Tone"
capability "Configuration"
capability “Music Player”attribute “status”, “enum”, [“off”, “alarm”, “customAlarm”, “delayedAlarm”, “beepDelayedAlarm”, “beep”, “beepSchedule”, “customBeep”, “customBeepSchedule”]
command “customAlarm”, [“number”, “number”, “number”]
command “delayedAlarm”, [“number”, “number”, “number”, “number”]
command “customBeep”, [“number”, “number”, “number”, “number”, “number”]
command "startBeep"
command "startBeepDelayedAlarm"
command “startCustomBeep”, [“number”, “number”, “number”, “number”, “number”, “number”, “number”]command "customBeep1"
command "customBeep2"
command "customBeep3"
command "customBeep4"
command "customBeep5"
command “customBeep6”// Music and Sonos Related Commands
command "playSoundAndTrack"
command "playTrackAndRestore"
command "playTrackAndResume"
command "playTextAndResume"
command “playTextAndRestore”fingerprint deviceId: “0x1005”, inClusters: “0x5E,0x98,0x25,0x70,0x85,0x59,0x72,0x2B,0x2C,0x86,0x7A,0x73”, outClusters: “0x5A,0x82”
}
simulator {
reply “9881002001FF,9881002002”: "command: 9881, payload: 002003FF"
reply “988100200100,9881002002”: “command: 9881, payload: 00200300”
}
preferences {
input "sirenSound", "number",
title: "Siren Sound (1-5)",
defaultValue: 1,
range: "1..5",
displayDuringSetup: true,
required: false
input "sirenVolume", "number",
title: "Siren Volume (1-3)",
defaultValue: 1,
range: "1..3",
displayDuringSetup: true,
required: false
input "alarmDuration", "number",
title: "Turn siren off after: (seconds)",
defaultValue: 0,
displayDuringSetup: true,
required: false
input "beepSound", "number",
title: "Beep Sound (1-5)",
defaultValue: 3,
range: "1..5",
displayDuringSetup: false,
required: false
input "beepVolume", "number",
title: "Beep Volume (1-3)",
defaultValue: 1,
range: "1..3",
displayDuringSetup: false,
required: false
input "beepRepeat", "number",
title: "Beep Repeat (1-100)",
defaultValue: 1,
range: "1..100",
displayDuringSetup: false,
required: false
input "beepRepeatDelay", "number",
title: "Time Between Beeps in Milliseconds",
defaultValue: 1000,
displayDuringSetup: false,
required: false
input "beepLength", "number",
title: "Length of Beep in Milliseconds",
defaultValue: 100,
displayDuringSetup: false,
required: false
input "beepEvery", "number",
title: "Scheduled Beep Every (seconds)",
defaultValue: 10,
displayDuringSetup: false,
required: false
input "beepStopAfter", "number",
title: "Stop Scheduled Beep After (seconds)",
defaultValue: 60,
displayDuringSetup: false,
required: false
input "useBeepDelayedAlarm", "bool",
title: "Play Beep Schedule Before Sounding Alarm?",
defaultValue: false,
displayDuringSetup: false,
required: false
input "debugOutput", "bool",
title: "Enable debug logging?",
defaultValue: true,
displayDuringSetup: false,
required: false
}
tiles(scale: 2) {
multiAttributeTile(name:"status", type: "generic", width: 6, height: 3, canChangeIcon: true){
tileAttribute ("status", key: "PRIMARY_CONTROL") {
attributeState "off", label:'off', action: "off", icon:"st.alarm.alarm.alarm", backgroundColor:"#ffffff"
attributeState "alarm", label:'Alarm Sounding!', action: "off", icon:"st.alarm.alarm.alarm", backgroundColor:"#ff9999"
attributeState "customAlarm", label:'Custom Alarm Sounding!', action: "off", icon:"", backgroundColor:"#ff9999"
attributeState "delayedAlarm", label:'Delayed Alarm Active!', action: "off", icon:"", backgroundColor:"#ff9999"
attributeState "beepDelayedAlarm", label:'Beep Delayed Alarm Active!', action: "off", icon:"", backgroundColor:"#ff9999"
attributeState "beep", label:'Beeping!', action: "off", icon:"st.Entertainment.entertainment2", backgroundColor:"#99FF99"
attributeState "beepSchedule", label:'Scheduled\nBeeping!', action: "off", icon:"", backgroundColor:"#99FF99"
attributeState "customBeep", label:'Custom Beeping!', action: "off", icon:"", backgroundColor:"#694489"
attributeState "customBeepSchedule", label:'Scheduled Custom Beeping!', action: "off", icon:"", backgroundColor:"#694489"
}
}
standardTile("playAlarm", "device.alarm", label: 'Alarm', width: 2, height: 2) {
state "default", label:'Alarm', action: "both", icon:"st.alarm.alarm.alarm", backgroundColor: "#ff9999"
state "both", label:'Stop', action: "off", icon:"st.alarm.alarm.alarm", backgroundColor: "#ffffff"
}
standardTile("playBeep", "device.status", label: 'Beep', width: 2, height: 2) {
state "default", label:'Beep', action:"beep", icon:"st.Entertainment.entertainment2", backgroundColor: "#99FF99"
}
valueTile("playBeepSchedule", "device.status", label: 'Scheduled Beep', width: 2, height: 2) {
state "default", label:'Start\nBeep', action:"startBeep",backgroundColor: "#99FF99"
state "beepSchedule", label:'Stop\nBeep', action:"off", icon: "", backgroundColor: "#ffffff"
}
valueTile("playCustomBeep1", "device.status", label: 'Custom Beep 1', width: 2, height: 2, wordWrap: true) {
state "default", label:'Beep\n1', action:"customBeep1",backgroundColor: "#694489"
}
valueTile("playCustomBeep2", "device.status", label: 'Custom Beep 2', width: 2, height: 2, wordWrap: true) {
state "default", label:'Beep\n2', action:"customBeep2",backgroundColor: "#694489"
}
valueTile("playCustomBeep3", "device.status", label: 'Custom Beep 3', width: 2, height: 2, wordWrap: true) {
state "default", label:'Beep\n3', action:"customBeep3",backgroundColor: "#694489"
}
valueTile("playCustomBeep4", "device.status", label: 'Custom Beep 4', width: 2, height: 2, wordWrap: true) {
state "default", label:'Beep\n4', action:"customBeep4",backgroundColor: "#694489"
}
valueTile("playCustomBeep5", "device.status", label: 'Custom Beep 5', width: 2, height: 2, wordWrap: true) {
state "default", label:'Beep\n5', action:"customBeep5",backgroundColor: "#694489"
}
valueTile("playCustomBeep6", "device.status", label: 'Custom Beep 6', width: 2, height: 2, wordWrap: true) {
state "default", label:'Beep\n6', action:"customBeep6",backgroundColor: "#694489"
}
main "status"
details(["status", "playAlarm", "playBeep", "playBeepSchedule", "playCustomBeep1", "playCustomBeep2", "playCustomBeep3", "playCustomBeep4", "playCustomBeep5", "playCustomBeep6"])
}
}
// Unsuported Music Player commands
def unmute() { handleUnsupportedCommand(“unmute”) }
def resumeTrack(map) { handleUnsupportedCommand(“resumeTrack”) }
def restoreTrack(map) { handleUnsupportedCommand(“restoreTrack”) }
def nextTrack() { handleUnsupportedCommand(“nextTrack”) }
def setLevel(number) { handleUnsupportedCommand(“setLevel”) }
def previousTrack() { handleUnsupportedCommand(“previousTrack”) }
def setTrack(string) { handleUnsupportedCommand(“setTrack”) }
// Turns siren off
def pause() { off() }
def stop() { off() }
def mute() { off() }
// Turns siren on
def play() { both() }
// Commands necessary for SHM, Notify w/ Sound, and Rule Machine TTS functionality.
def playSoundAndTrack(text, other, other2, other3) {
playTrackAndResume(text, other, other2)
}
def playTrackAndRestore(text, other, other2) {
playTrackAndResume(text, other, other2)
}
def playTrackAndResume(text, other, other2) {
playText(getTextFromTTSUrl(text))
}
def getTextFromTTSUrl(ttsUrl) {
def urlPrefix = “https://s3.amazonaws.com/smartapp-media/tts/“
if (ttsUrl?.toString()?.toLowerCase()?.contains(urlPrefix)) {
return ttsUrl.replace(urlPrefix,””).replace(".mp3","")
}
return ttsUrl
}
def playTextAndResume(text, other) { playText(text) }
def playTextAndRestore(text, other) { playText(text) }
def playTrack(text) { playText(text) }
def playText(text) {
text = cleanTextCmd(text)
def cmds
switch (text) {
case [“siren”, “strobe”, “both”, “on”, “play”]:
cmds = both()
break
case [“stop”, “off”, “pause”, “mute”]:
cmds = off()
break
case “beep”:
cmds = beep()
break
case “startbeep”:
cmds = startBeep()
break
case “startbeepdelayedalarm”:
cmds = startBeepDelayedAlarm()
break
case “custombeep1”:
cmds = customBeep1()
break
case “custombeep2”:
cmds = customBeep2()
break
case “custombeep3”:
cmds = customBeep3()
break
case “custombeep4”:
cmds = customBeep4()
break
case “custombeep5”:
cmds = customBeep5()
break
case “custombeep6”:
cmds = customBeep6()
break
default:
if (text) {
cmds = parseComplexCommand(text)
}
}
if (!cmds) {
logDebug “’$text’ is not a valid command.”
}
else {
return cmds
}
}
def cleanTextCmd(text) {
return text?.
toLowerCase()?.
replace(",", “_”)?.
replace(" “, “”)?.
replace(”(", “”)?.
replace(")", “”)
}
def parseComplexCommand(text) {
def cmds = []
def args = getComplexCmdArgs(text)
switch (args?.size()) {
case 3:
cmds += customAlarm(
args[0],
args[1],
args[2])
break
case 4:
cmds += delayedAlarm(
args[0],
args[1],
args[2],
args[3])
break
case 5:
cmds += customBeep(
args[0],
args[1],
args[2],
args[3],
args[4])
break
case 7:
cmds += startCustomBeep(
args[0],
args[1],
args[2],
args[3],
args[4],
args[5],
args[6])
break
}
return cmds
}
private getComplexCmdArgs(text) {
for (prefix in [“customalarm”, “delayedalarm”,“startcustombeep”,“custombeep”]) {
text = removeCmdPrefix(text, prefix)
}
def args = text.tokenize("_")
if (args.every { node -> isNumeric(node) }) {
return args
}
else {
return null
}
}
private removeCmdPrefix(text, prefix) {
if (text.startsWith(prefix)) {
return text.replace("$prefix_", “”).replace(prefix, “”)
}
else {
return text
}
}
// Turns on siren and strobe
def on() {
both()
}
// Turns off siren and strobe
def off() {
logDebug "Executing off() command"
changeStatus(“off”)
turnOff()
}
private turnOff() {
secureDelayBetween([
switchOffSetCmd(),
switchGetCmd()
])
}
// Turns on siren and strobe
def strobe() {
both()
}
// Turns on siren and strobe
def siren() {
both()
}
// Turns on siren and strobe
def both() {
logDebug “Executing both() command”
if (settings.useBeepDelayedAlarm) {
startBeepDelayedAlarm()
}
else {
changeStatus("alarm")
playDefaultAlarm()
}
}
// Repeatedly plays the default beep based on the beepEvery and beepStopAfter settings and then turns on alarm.
def startBeepDelayedAlarm() {
changeStatus(“beepDelayedAlarm”)
startDefaultBeepSchedule()
}
private playPendingAlarm() {
state.alarmPending = false
if (state.scheduledAlarm) {
def sound = state.scheduledAlarm?.sound
def volume = state.scheduledAlarm?.volume
def duration = state.scheduledAlarm?.duration
state.scheduledAlarm = null
customAlarm(sound, volume, duration)
}
else {
playDefaultAlarm()
}
}
private playDefaultAlarm() {
playAlarm(settings.sirenSound, settings.sirenVolume, settings.alarmDuration)
}
// Plays sound at volume for duration.
def customAlarm(sound, volume, duration) {
changeStatus(“customAlarm”)
playAlarm(sound, volume, duration)
}
private playAlarm(sound, volume, duration) {
def durationMsg = (duration && duration > 0) ? “, duration: $duration” : ""
logDebug “Sounding Alarm: [sound: $sound, volume: $volume$durationMsg]”
sound = validateSound(sound)
volume = validateVolume(volume)
duration = validateRange(duration, 0, 0, Integer.MAX_VALUE, "Alarm Duration")
if (currentStatus() in ["alarm", "delayedAlarm", "beepDelayedAlarm"]) {
sendEvent(name:"alarm", value: "both", isStateChange: true)
sendEvent(name:"switch", value: "on", isStateChange: true, displayed: false)
}
def cmds = []
cmds << secureCmd(sirenSoundVolumeSetCmd(sound, volume))
if (duration > 0) {
cmds << "delay ${duration * 1000}"
cmds += turnOff()
}
return cmds
}
def delayedAlarm(sound, volume, duration, delay) {
changeStatus(“delayedAlarm”)
startDelayedAlarm(sound, volume, duration, delay)
}
private startDelayedAlarm(sound, volume, duration, delay) {
state.scheduledAlarm = [
“sound”: sound,
“volume”: volume,
“duration”: duration
]
delay = validateRange(delay, 3, 1, Integer.MAX_VALUE, "delay")
logDebug "Starting ${currentStatus()} [sound: $sound, volume: $volume, duration: $duration, delay: $delay]"
[
"delay ${delay * 1000}",
secureCmd(zwave.basicV1.basicGet())
]
}
// Plays the default beep.
def beep() {
changeStatus(“beep”)
playDefaultBeep()
}
private playDefaultBeep() {
playBeep(
settings.beepSound,
settings.beepVolume,
settings.beepRepeat,
settings.beepRepeatDelay,
settings.beepLength
)
}
// Plays short beep.
def customBeep1() {
customBeep(3, 1, 1, 0, 50)
}
// Plays medium beep
def customBeep2() {
customBeep(3, 1, 1, 0, 100)
}
// Plays long beep
def customBeep3() {
customBeep(3, 1, 1, 0, 250)
}
// Plays 3 short beeps
def customBeep4() {
customBeep(3, 1, 3, 0, 50)
}
// Plays 3 medium beeps
def customBeep5() {
customBeep(3, 1, 3, 100 , 100)
}
// Plays 3 long beeps
def customBeep6() {
customBeep(3, 1, 3, 150, 200)
}
// Repeatedly plays the default beep based on the beepEvery and beepStopAfter settings.
def startBeep() {
changeStatus(“beepSchedule”)
startDefaultBeepSchedule()
}
private startDefaultBeepSchedule() {
startBeepSchedule(
settings.beepEvery,
settings.beepStopAfter,
settings.beepSound,
settings.beepVolume,
settings.beepRepeat,
settings.beepRepeatDelay,
settings.beepLength
)
}
// Repeatedly plays specified beep at specified in specified intervals.
def startCustomBeep(beepEverySeconds, stopAfterSeconds, sound, volume, repeat=1, repeatDelayMS=1000, beepLengthMS=100) {
changeStatus(“customBeepSchedule”)
startBeepSchedule(beepEverySeconds, stopAfterSeconds, sound, volume, repeat, repeatDelayMS, beepLengthMS)
}
private startBeepSchedule(beepEverySeconds, stopAfterSeconds, sound, volume, repeat, repeatDelayMS, beepLengthMS) {
logDebug “Starting ${currentStatus()} [beepEverySeconds: $beepEverySeconds, stopAfterSeconds: $stopAfterSeconds]”
state.beepSchedule = [
"startTime": (new Date().time),
"beepEvery": validateBeepEvery(beepEverySeconds),
"stopAfter": validateBeepStopAfter(stopAfterSeconds),
"sound": sound,
"volume": volume,
"repeat": repeat,
"repeatDelay": repeatDelayMS,
"beepLength": beepLengthMS
]
return playScheduledBeep()
}
private playScheduledBeep() {
def beepSchedule = state.beepSchedule
def cmds = []
if (beepScheduleStillActive(beepSchedule?.startTime, beepSchedule?.stopAfter)) {
cmds += playBeep(
beepSchedule.sound,
beepSchedule.volume,
beepSchedule.repeat,
beepSchedule.repeatDelay,
beepSchedule.beepLength
)
}
if (nextScheduledBeepStillActive()) {
if (beepSchedule.beepEvery > 0) {
cmds << "delay ${beepSchedule.beepEvery * 1000}"
}
cmds << secureCmd(zwave.basicV1.basicGet())
}
else {
state.beepSchedule = null
state.beepScheduleRunning = false
if (state.alarmPending) {
cmds += playPendingAlarm()
}
else {
cmds += turnOff()
}
}
return cmds
}
private nextScheduledBeepStillActive() {
def sched = state.beepSchedule
if (sched?.beepEvery != null) {
def adjustedStartTime = (sched.startTime - (sched.beepEvery * 1000))
return beepScheduleStillActive(adjustedStartTime, sched.stopAfter)
}
else {
return false
}
}
private beepScheduleStillActive(startTime, stopAfter) {
if (startTime && stopAfter) {
def endTimeMS = startTime + (stopAfter * 1000)
return (new Date().time < endTimeMS) && state.beepScheduleRunning
}
else {
return false
}
}
// Plays specified beep.
def customBeep(sound, volume, repeat=1, repeatDelayMS=1000, beepLengthMS=100) {
changeStatus(“customBeep”)
playBeep(sound, volume, repeat, repeatDelayMS, beepLengthMS)
}
private playBeep(sound, volume, repeat, repeatDelayMS, beepLengthMS) {
logDebug “${currentStatus()} [sound: $sound, volume: $volume, repeat: $repeat, repeatDelayMS: $repeatDelayMS, beepLengthMS: $beepLengthMS]”
int maxMS = 18000
sound = validateSound(sound, 3)
volume = validateVolume(volume)
beepLengthMS = validateRange(beepLengthMS, 100, 0, maxMS, "Beep Length")
repeatDelayMS = validateRepeatDelay(repeatDelayMS, beepLengthMS, maxMS)
repeat = validateRepeat(repeat, beepLengthMS, repeatDelayMS, maxMS)
def cmds = []
for (int repeatIndex = 1; repeatIndex <= repeat; repeatIndex++) {
cmds << secureCmd(sirenSoundVolumeSetCmd(sound, volume))
if (beepLengthMS > 0) {
cmds << "delay $beepLengthMS"
}
cmds << secureCmd(switchOffSetCmd())
if (repeat > 1 && repeatDelayMS > 0) {
cmds << "delay $repeatDelayMS"
}
}
if (!state.beepScheduleRunning && !state.alarmPending && currentStatus() != "off") {
cmds << turnOff()
}
return cmds
}
private changeStatus(newStatus) {
def oldStatus = currentStatus()
finalizeOldStatus(oldStatus, newStatus)
if (newStatus in ["delayedAlarm", "beepDelayedAlarm"]) {
state.alarmPending = true
}
if (newStatus in ["beepDelayedAlarm", "beepSchedule", "customBeepSchedule"]) {
state.beepScheduleRunning = true
}
def displayStatus = (
oldStatus != newStatus &&
newStatus != "alarm" &&
!(oldStatus in ["alarm", "delayedAlarm", "beepDelayedAlarm"]))
sendEvent(name:"status", value: newStatus, isStateChange: true, displayed: displayStatus)
}
private finalizeOldStatus(oldStatus, newStatus) {
if (state.alarmPending &&
oldStatus in [“delayedAlarm”, “beepDelayedAlarm”] &&
!(newStatus in [“alarm”, “customAlarm”])) {
logDebug “Delayed Alarm Cancelled”
}
else if (state.beepScheduleRunning) {
if (nextScheduledBeepStillActive()) {
logDebug “Beep Schedule Cancelled”
}
else {
logDebug “Beep Schedule Completed”
}
}
state.alarmPending = false
state.beepScheduleRunning = false
state.scheduledAlarm = null
state.beepSchedule = null
}
// Checks if the device supports security commands.
def configure() {
logDebug "Checking for secure inclusion"
state.useSecureCommands = null
def cmds = secureDelayBetween([
supportedSecurityGetCmd(),
sendNotificationsSetCmd()
])
state.useSecureCommands = false
response(cmds)
}
// Stores preferences and displays device settings.
def updated() {
if (!isDuplicateCommand(state.lastUpdated, 1000)) {
state.lastUpdated = new Date().time
state.debugOutput = validateBool(debugOutput, true)
logDebug “Updating”
def cmds = []
if (!state.useSecureCommands) {
logDebug "Checking for Secure Command Support"
state.useSecureCommands = null
cmds << secureCmd(supportedSecurityGetCmd())
}
cmds << secureCmd(zwave.firmwareUpdateMdV2.firmwareMdGet())
cmds += turnOff()
response(cmds)
}
}
private isDuplicateCommand(lastExecuted, allowedMil) {
!lastExecuted ? false : (lastExecuted + allowedMil > new Date().time)
}
private sirenSoundVolumeSetCmd(int sound, int volume) {
zwave.configurationV1.configurationSet(parameterNumber: 37, size: 2, configurationValue: [validateSound(sound), validateVolume(volume)])
}
private sendNotificationsSetCmd() {
zwave.configurationV1.configurationSet(parameterNumber: 80, size: 1, scaledConfigurationValue: 0)
}
private switchOffSetCmd() {
zwave.switchBinaryV1.switchBinarySet(switchValue: 0)
}
private switchGetCmd() {
zwave.switchBinaryV1.switchBinaryGet()
}
private supportedSecurityGetCmd() {
zwave.securityV1.securityCommandsSupportedGet()
}
// Parses incoming message warns if not paired securely
def parse(String description) {
def result = null
if (description.startsWith(“Err 106”)) {
state.useSecureCommands = false
def msg = "Secure Inclusion Failed. You may need to remove and add the device again while pushing the action button repeatedly during the Inclusion process."
log.warn "$msg"
result = createEvent( name: “secureInclusion”, value: “failed”, isStateChange: true, descriptionText: “$msg”)
}
else if (description != null && description != “updated”) {
def cmd = zwave.parse(description, [0x98: 1, 0x20: 1, 0x70: 1, 0x7A: 2, 0x25: 1])
if (cmd != null) {
result = zwaveEvent(cmd)
}
}
result
}
// Unencapsulates the secure command.
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
if (cmd != null) {
def encapCmd = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1, 0x7A: 2, 0x25: 1])
if (encapCmd) {
zwaveEvent(encapCmd)
}
}
}
// Enables secure command setting.
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
state.useSecureCommands = true
logInfo “Secure Commands Supported”
}
// Writes firmware to the Info Log.
def zwaveEvent(physicalgraph.zwave.commands.firmwareupdatemdv2.FirmwareMdReport cmd) {
logInfo “Firmware: $cmd”
}
// Handles device reporting off and alarm turning on.
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
if (cmd.value == 0) {
changeStatus("off")
def alarmDisplayed = (device.currentValue("alarm") == "both")
if (alarmDisplayed) {
logDebug "Alarm is off"
}
[
createEvent(name:"alarm", value: "off", isStateChange: true, displayed: alarmDisplayed),
createEvent(name:"switch", value: "off", isStateChange: true, displayed: false)
]
}
}
// Handles the scheduling of beeps.
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
[
response(playScheduledBeep())
]
}
// Writes unexpected commands to debug log
def zwaveEvent(physicalgraph.zwave.Command cmd) {
logDebug("$cmd")
createEvent(descriptionText: cmd.toString(), isStateChange: false)
}
private secureDelayBetween(cmds, delay=100) {
delayBetween(cmds.collect{ secureCmd(it) }, delay)
}
private secureCmd(physicalgraph.zwave.Command cmd) {
if (state.useSecureCommands == null || state.useSecureCommands) {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
else {
cmd.format()
}
}
private int validateSound(sound, int defaultSound=1) {
return validateRange(sound, defaultSound, 1, 5, “Sound”)
}
private int validateVolume(volume, int defaultVolume=1) {
return validateRange(volume, defaultVolume, 1, 3, “Volume”)
}
private int validateRepeatDelay(repeatDelayMS, int beepLengthMS, int maxMS) {
int repeatDelayMaxMS = (beepLengthMS == maxMS) ? 0 : (maxMS - beepLengthMS)
return validateRange(repeatDelayMS, 1000, 0, repeatDelayMaxMS, “Repeat Delay”)
}
private int validateRepeat(repeat, int beepLengthMS, int repeatDelayMS, int maxMS) {
int combinedMS = (beepLengthMS + repeatDelayMS)
int maxRepeat = (combinedMS >= maxMS) ? 0 : (maxMS / combinedMS).toInteger()
return validateRange(repeat, 1, 0, maxRepeat, “Repeat”)
}
private int validateBeepEvery(seconds) {
validateRange(seconds, 10, 0, Integer.MAX_VALUE, “Beep Every”)
}
private int validateBeepStopAfter(seconds) {
validateRange(seconds, 60, 2, Integer.MAX_VALUE, “Beep Stop After”)
}
private int validateRange(val, defaultVal, minVal, maxVal, desc) {
def result
def errorType = null
if (isNumeric(val)) {
result = val.toInteger()
}
else {
errorType = "invalid"
result = defaultVal
}
if (result > maxVal) {
errorType = "too high"
result = maxVal
} else if (result < minVal) {
errorType = "too low"
result = minVal
}
if (errorType) {
logDebug("$desc: $val is $errorType, using $result instead.")
}
result
}
private isNumeric(val) {
return val?.toString()?.isNumber()
}
private validateBool(val, defaulVal) {
if (val == null) {
defaultVal
}
else {
(val == true || val == “true”)
}
}
private currentStatus() {
return device.currentValue(“status”) ? device.currentValue(“status”) : “”
}
private handleUnsupportedCmd(cmd) {
logDebug “Command $cmd not supported”
}
private logDebug(msg) {
if (state.debugOutput || state.debugOutput == null) {
log.debug “$msg”
}
}
private logInfo(msg) {
log.info “${device.displayName} - $msg”
}