Compare commits

...

5 Commits

Author SHA1 Message Date
Nowak
215878ddc1 MSA-972: Updated Device Handler for Fibaro Smoke Sensor:
- updated tamper attrbiute,
- removed unused smoke/heat alarm events/states,
- updated tiles,
- corrected parameter values in preferences,
- added handling for more notifications (replace battery soon, system hardware failure),
- added Sensor Alarm Report handling and Sensor Alarm Get on wake up,
- added handling for received CRC16 frames,
- improved sent frame encapsulation (CRC16/Security)
2016-03-21 04:10:27 -05:00
Vinay Rao
42865abc55 Merge pull request #651 from SmartThingsCommunity/staging
Rolling down changes in staging to master
2016-03-18 13:11:08 -07:00
tslagle13
410e9f40cc Merge pull request #633 from tslagle13/tts-fixes
Fix text-to-speech feature
2016-03-14 11:08:45 -07:00
tslagle13
1578c48440 Fix text-to-speech feature
Refactor case statement so custom message works. Remove comment from input selection.
2016-03-13 17:04:15 -07:00
Vinay Rao
7526d2b445 Merge pull request #628 from SmartThingsCommunity/master
Rolling up master to staging
2016-03-10 18:35:42 -08:00
2 changed files with 182 additions and 80 deletions

View File

@@ -18,9 +18,10 @@ metadata {
capability "Sensor"
capability "Smoke Detector" //attributes: smoke ("detected","clear","tested")
capability "Temperature Measurement" //attributes: temperature
attribute "tamper", "enum", ["detected", "clear"]
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
attribute "tamper", "enum", ["active", "inactive"]//capability "Tamper Alert"// <- yields "java.lang.RuntimeException: Metadata Error: Capability 'Tamper Alert' not found" error
attribute "heatAlarm", "enum", ["overheat", "inactive"]
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A"
}
simulator {
//battery
@@ -50,7 +51,7 @@ metadata {
input description: "Please consult Fibaro Smoke Sensor operating manual for advanced setting options. You can skip this configuration to use default settings",
title: "Advanced Configuration", displayDuringSetup: true, type: "paragraph", element: "paragraph"
input "smokeSensorSensitivity", "enum", title: "Smoke Sensor Sensitivity", options: ["High","Medium","Low"], defaultValue: "${smokeSensorSensitivity}", displayDuringSetup: true
input "zwaveNotificationStatus", "enum", title: "Notifications Status", options: ["disabled","casing opened","exceeding temperature threshold", "lack of Z-Wave range", "all notifications"],
input "zwaveNotificationStatus", "enum", title: "Notifications Status", options: ["disabled","casing opened","exceeding temperature threshold","all notifications"],
defaultValue: "${zwaveNotificationStatus}", displayDuringSetup: true
input "visualIndicatorNotificationStatus", "enum", title: "Visual Indicator Notifications Status",
options: ["disabled","casing opened","exceeding temperature threshold", "lack of Z-Wave range", "all notifications"],
@@ -61,44 +62,47 @@ metadata {
input "temperatureReportInterval", "enum", title: "Temperature Report Interval",
options: ["reports inactive", "5 minutes", "15 minutes", "30 minutes", "1 hour", "6 hours", "12 hours", "18 hours", "24 hours"], defaultValue: "${temperatureReportInterval}", displayDuringSetup: true
input "temperatureReportHysteresis", "number", title: "Temperature Report Hysteresis", description: "Available settings: 1-100 C", range: "1..100", displayDuringSetup: true
input "temperatureThreshold", "number", title: "Overheat Temperature Threshold", description: "Available settings: 0 or 2-100 C", range: "0..100", displayDuringSetup: true
input "temperatureThreshold", "number", title: "Overheat Temperature Threshold", description: "Available settings: 1-100 C", range: "1..100", displayDuringSetup: true
input "excessTemperatureSignalingInterval", "enum", title: "Excess Temperature Signaling Interval",
options: ["5 minutes", "15 minutes", "30 minutes", "1 hour", "6 hours", "12 hours", "18 hours", "24 hours"], defaultValue: "${excessTemperatureSignalingInterval}", displayDuringSetup: true
input "lackOfZwaveRangeIndicationInterval", "enum", title: "Lack of Z-Wave Range Indication Interval",
options: ["5 minutes", "15 minutes", "30 minutes", "1 hour", "6 hours", "12 hours", "18 hours", "24 hours"], defaultValue: "${lackOfZwaveRangeIndicationInterval}", displayDuringSetup: true
}
tiles (scale: 2){
multiAttributeTile(name:"smoke", type: "lighting", width: 6, height: 4){
multiAttributeTile(name:"FGSD", type: "lighting", width: 6, height: 4){
tileAttribute ("device.smoke", key: "PRIMARY_CONTROL") {
attributeState("clear", label:"CLEAR", icon:"st.alarm.smoke.clear", backgroundColor:"#ffffff")
attributeState("detected", label:"SMOKE", icon:"st.alarm.smoke.smoke", backgroundColor:"#e86d13")
attributeState("tested", label:"TEST", icon:"st.alarm.smoke.test", backgroundColor:"#e86d13")
attributeState("replacement required", label:"REPLACE", icon:"st.alarm.smoke.test", backgroundColor:"#FFFF66")
attributeState("unknown", label:"UNKNOWN", icon:"st.alarm.smoke.test", backgroundColor:"#ffffff")
}
tileAttribute ("device.battery", key: "SECONDARY_CONTROL") {
attributeState "battery", label:'Battery: ${currentValue}%', unit:"%"
}
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
}
}
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:"%"
}
valueTile("temperature", "device.temperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "temperature", label:'${currentValue}°', unit:"C"
}
valueTile("heatAlarm", "device.heatAlarm", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "clear", label:'TEMPERATURE OK', backgroundColor:"#ffffff"
state "overheat detected", label:'OVERHEAT DETECTED', backgroundColor:"#ffffff"
state "rapid temperature rise", label:'RAPID TEMP RISE', backgroundColor:"#ffffff"
state "underheat detected", label:'UNDERHEAT DETECTED', backgroundColor:"#ffffff"
}
valueTile("tamper", "device.tamper", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "clear", label:'NO TAMPER', backgroundColor:"#ffffff"
state "detected", label:'TAMPER DETECTED', backgroundColor:"#ffffff"
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
state "temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
}
valueTile("heatAlarm", "device.heatAlarm", inactiveLabel: false, width: 2, height: 2) {
state "inactive", label:'TEMPERATURE OK', backgroundColor:"#ffffff"
state "overheat", label:'OVERHEAT DETECTED', backgroundColor:"#bc2323"
}
main "smoke"
details(["smoke","temperature"])
main "FGSD"
details(["FGSD","temperature", "battery", "heatAlarm"])
}
}
@@ -117,7 +121,7 @@ def parse(String description) {
"If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
} else if (description != "updated") {
log.debug "parse() >> zwave.parse(description)"
def cmd = zwave.parse(description, [0x31: 5, 0x71: 3, 0x84: 1])
def cmd = zwave.parse(description, [0x31: 5, 0x5A: 1, 0x71: 3, 0x72: 2, 0x84: 1, 0x86: 1, 0x8B: 1, 0x9C: 1])
if (cmd) {
result = zwaveEvent(cmd)
}
@@ -126,14 +130,40 @@ def parse(String description) {
return result
}
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
log.info "Executing zwaveEvent 86 (VersionV1): 12 (VersionReport) with cmd: $cmd"
def fw = "${cmd.applicationVersion}.${cmd.applicationSubVersion}"
updateDataValue("fw", fw)
def text = "$device.displayName: firmware version: $fw, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}"
createEvent(descriptionText: text, isStateChange: false)
//security
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
setSecured()
def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x5A: 1, 0x71: 3, 0x84: 1, 0x9C: 1])
if (encapsulatedCommand) {
log.debug "command: 98 (Security) 81(SecurityMessageEncapsulation) encapsulatedCommand: $encapsulatedCommand"
zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
createEvent(descriptionText: cmd.toString())
}
}
//crc16
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
{
def versions = [0x31: 5, 0x72: 2, 0x86: 1, 0x8B: 1]
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (!encapsulatedCommand) {
log.debug "Could not extract command from $cmd"
} else {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
log.info "Executing zwaveEvent 86 (VersionV1): 12 (VersionReport) with cmd: $cmd"
def version = "${cmd.applicationVersion}.${cmd.applicationSubVersion}"
updateDataValue("version", fw)
def text = "$device.displayName: firmware version: $version, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}"
createEvent(descriptionText: text, isStateChange: false)
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: "battery", unit: "%" ]
@@ -161,18 +191,6 @@ def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationRejec
createEvent(displayed: true, descriptionText: "$device.displayName rejected the last request")
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
setSecured()
def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x71: 3, 0x84: 1])
if (encapsulatedCommand) {
log.debug "command: 98 (Security) 81(SecurityMessageEncapsulation) encapsulatedCommand: $encapsulatedCommand"
zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
createEvent(descriptionText: cmd.toString())
}
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
log.info "Executing zwaveEvent 98 (SecurityV1): 03 (SecurityCommandsSupportedReport) with cmd: $cmd"
setSecured()
@@ -199,18 +217,31 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
if (cmd.notificationType == 7) {
switch (cmd.event) {
case 0:
result << createEvent(name: "tamper", value: "clear", displayed: false)
result << createEvent(name: "tamper", value: "inactive", displayed: false)
break
case 3:
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName casing was opened")
result << createEvent(name: "tamper", value: "active", descriptionText: "$device.displayName casing was opened")
break
}
} else if (cmd.notificationType == 1) { //Smoke Alarm (V2)
log.debug "notificationv3.NotificationReport: for Smoke Alarm (V2)"
result << smokeAlarmEvent(cmd.event)
} else if (cmd.notificationType == 4) { // Heat Alarm (V2)
} else if (cmd.notificationType == 4) { // Heat Alarm (V2)
log.debug "notificationv3.NotificationReport: for Heat Alarm (V2)"
result << heatAlarmEvent(cmd.event)
} else if (cmd.notificationType == 8) {
if (cmd.event == 0x0A) {
def map = [:]
map.name = "battery"
map.value = 1
map.unit = "%"
map.displayed = true
result << createEvent(map)
}
} else if (cmd.notificationType == 9) {
if (cmd.event == 0x01) {
result << createEvent(descriptionText: "Warning: $device.displayName system hardware failure", isStateChange: true)
}
} else {
log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
result << createEvent(descriptionText: cmd.toString(), isStateChange: false)
@@ -221,7 +252,7 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
def smokeAlarmEvent(value) {
log.debug "smokeAlarmEvent(value): $value"
def map = [name: "smoke"]
if (value == 1 || value == 2) {
if (value == 2) {
map.value = "detected"
map.descriptionText = "$device.displayName detected smoke"
} else if (value == 0) {
@@ -230,9 +261,6 @@ def smokeAlarmEvent(value) {
} else if (value == 3) {
map.value = "tested"
map.descriptionText = "$device.displayName smoke alarm test"
} else if (value == 4) {
map.value = "replacement required"
map.descriptionText = "$device.displayName replacement required"
} else {
map.value = "unknown"
map.descriptionText = "$device.displayName unknown event"
@@ -243,18 +271,12 @@ def smokeAlarmEvent(value) {
def heatAlarmEvent(value) {
log.debug "heatAlarmEvent(value): $value"
def map = [name: "heatAlarm"]
if (value == 1 || value == 2) {
map.value = "overheat detected"
if (value == 2) {
map.value = "overheat"
map.descriptionText = "$device.displayName overheat detected"
} else if (value == 0) {
map.value = "clear"
map.value = "inactive"
map.descriptionText = "$device.displayName heat alarm cleared (no overheat)"
} else if (value == 3 || value == 4) {
map.value = "rapid temperature rise"
map.descriptionText = "$device.displayName rapid temperature rise"
} else if (value == 5 || value == 6) {
map.value = "underheat detected"
map.descriptionText = "$device.displayName underheat detected"
} else {
map.value = "unknown"
map.descriptionText = "$device.displayName unknown event"
@@ -274,12 +296,14 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) {
//Only ask for battery if we haven't had a BatteryReport in a while
if (!state.lastbatt || (new Date().time) - state.lastbatt > 24*60*60*1000) {
log.debug("Device has been configured sending >> batteryGet()")
cmds << zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format()
cmds << "delay 1200"
cmds << zwave.batteryV1.batteryGet()
}
cmds << zwave.sensorAlarmV1.sensorAlarmGet(sensorType: 0)
cmds << zwave.sensorAlarmV1.sensorAlarmGet(sensorType: 1)
cmds << zwave.sensorAlarmV1.sensorAlarmGet(sensorType: 4)
log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
result << response(cmds) //tell device back to sleep
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation()
result << response(commands(cmds,500)) //tell device back to sleep
}
result
}
@@ -317,8 +341,26 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
log.debug "After device is securely joined, send commands to update tiles"
result << zwave.batteryV1.batteryGet()
result << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x01)
result << zwave.wakeUpV1.wakeUpNoMoreInformation()
[[descriptionText:"${device.displayName} MSR report"], response(commands(result, 5000))]
//always send wakeUpNoMoreInformation as secure (at this point "secured" property is not set)
[[descriptionText:"${device.displayName} MSR report"], response(commands(result, 500) << "delay 1000" << secure(zwave.wakeUpV1.wakeUpNoMoreInformation()))]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
log.debug "deviceIdData: ${cmd.deviceIdData}"
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
log.debug "deviceIdType: ${cmd.deviceIdType}"
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
String serialNumber = "h'"
cmd.deviceIdData.each{ data ->
serialNumber += "${String.format("%02X", data)}"
}
updateDataValue("serialNumber", serialNumber)
log.debug "${device.displayName} - serial number: ${serialNumber}"
}
}
def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationReport cmd) {
@@ -332,6 +374,37 @@ def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationReport cmd)
result
}
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
def map = [:]
switch (cmd.sensorType) {
case 0:
map.name = "tamper"
map.value = cmd.sensorState == 0xFF ? "active" : "inactive"
map.descriptionText = cmd.sensorState == 0xFF ? "$device.displayName casing was opened" : ""
break
case 1:
map.name = "smoke"
map.value = cmd.sensorState == 0xFF ? "detected" : "clear"
map.descriptionText = cmd.sensorState == 0xFF ? "$device.displayName detected smoke" : "$device.displayName is clear (no smoke)"
break
case 4:
map.name = "heatAlarm"
map.value = cmd.sensorState == 0xFF ? "overheat" : "inactive"
map.descriptionText = cmd.sensorState == 0xFF ? "$device.displayName overheat detected" : "$device.displayName heat alarm cleared (no overheat)"
break
}
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.timeparametersv1.TimeParametersGet cmd) {
log.info "Executing zwaveEvent 8B (TimeParametersV1) : 02 (TimeParametersGet) with cmd: $cmd"
def nowCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
//Time Parameters are requested by an un-encapsulated frame
response(zwave.timeParametersV1.timeParametersReport(year: nowCal.get(Calendar.YEAR), month: (nowCal.get(Calendar.MONTH) + 1), day: nowCal.get(Calendar.DAY_OF_MONTH),
hourUtc: nowCal.get(Calendar.HOUR_OF_DAY), minuteUtc: nowCal.get(Calendar.MINUTE), secondUtc: nowCal.get(Calendar.SECOND)).format())
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.warn "General zwaveEvent cmd: ${cmd}"
createEvent(descriptionText: cmd.toString(), isStateChange: false)
@@ -360,7 +433,7 @@ def configure() {
}
//3. Z-Wave notification status: 0-all disabled (default), 1-casing open enabled, 2-exceeding temp enable
if (zwaveNotificationStatus && zwaveNotificationStatus != "null"){
request += zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, scaledConfigurationValue: notificationOptionValueMap[zwaveNotificationStatus] ?: 0)
request += zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, scaledConfigurationValue: zwaveNotificationOptionValueMap[zwaveNotificationStatus] ?: 0)
}
//4. Visual indicator notification status: 0-all disabled (default), 1-casing open enabled, 2-exceeding temp enable, 4-lack of range notification
if (visualIndicatorNotificationStatus && visualIndicatorNotificationStatus != "null") {
@@ -402,7 +475,16 @@ def configure() {
//12. get temperature reading from device
request += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x01)
commands(request) + ["delay 10000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
//13. set association group
request += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId])
//14. get version
request += zwave.versionV1.versionGet()
//15. get serial number
request += zwave.manufacturerSpecificV2.deviceSpecificGet()
commands(request) + ["delay 1000", command(zwave.wakeUpV1.wakeUpNoMoreInformation())]
}
}
@@ -418,24 +500,42 @@ private def getTimeOptionValueMap() { [
"reports inactive" : 0,
]}
private def getZwaveNotificationOptionValueMap() { [
"disabled" : 0,
"casing opened" : 1,
"exceeding temperature threshold" : 2,
"all notifications" : 3
]}
private def getNotificationOptionValueMap() { [
"disabled" : 0,
"casing opened" : 1,
"exceeding temperature threshold" : 2,
"lack of Z-Wave range" : 4,
"all notifications" : 7,
"disabled" : 0,
"casing opened" : 1,
"exceeding temperature threshold" : 2,
"lack of Z-Wave range" : 4,
"all notifications" : 7
]}
private command(physicalgraph.zwave.Command cmd) {
if (isSecured()) {
def secureClasses = [0x20, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
if (isSecured() && secureClasses.find{ it == cmd.commandClassId }) {
log.info "Sending secured command: ${cmd}"
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
secure(cmd)
} else {
log.info "Sending unsecured command: ${cmd}"
cmd.format()
crc16(cmd)
}
}
private secure(physicalgraph.zwave.Command cmd) {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
private crc16(physicalgraph.zwave.Command cmd) {
//zwave.crc16encapV1.crc16Encap().encapsulate(cmd).format()
"5601${cmd.format()}0000"
}
private commands(commands, delay=200) {
log.info "inside commands: ${commands}"
delayBetween(commands.collect{ command(it) }, delay)
@@ -452,4 +552,4 @@ private setSecured() {
}
private isSecured() {
getDataValue("secured") == "true"
}
}

View File

@@ -76,7 +76,7 @@ def mainPage() {
}
section{
input "actionType", "enum", title: "Action?", required: true, defaultValue: "Bell 1", options: [
//"Custom Message",
"Custom Message",
"Bell 1",
"Bell 2",
"Dogs Barking",
@@ -89,7 +89,7 @@ def mainPage() {
"Someone is arriving",
"Piano",
"Lightsaber"]
//input "message","text",title:"Play this message", required:false, multiple: false
input "message","text",title:"Play this message", required:false, multiple: false
}
section {
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
@@ -408,13 +408,15 @@ private loadText() {
case "Lightsaber":
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/lightsaber.mp3", duration: "10"]
break;
default:
/*if (message) {
case "Custom Message":
if (message) {
state.sound = textToSpeech(message instanceof List ? message[0] : message) // not sure why this is (sometimes) needed)
}
else {
state.sound = textToSpeech("You selected the custom message option but did not enter a message in the $app.label Smart App")
}*/
}
break;
default:
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/bell1.mp3", duration: "10"]
break;
}