Smartthings app crashing when opening device

I’ve had a device and device handler working fine for most of the year. Hadn’t used the device in a couple months but recently went to view via Smartthings mobile app Things menu and app simply goes blank and exits. I’m using iPhone 6 on IOS 10.3.3. Am wondering if something has changed in Smartthings platform that broke my device. Maybe some problem with my tile definitions?

I’ve tried forcing an update to the device handler via the IDE but didn’t resolve.

Note that this device uses a service manager app to communicate via UPnP to a Raspberry Pi. The service manager appears to be alive and well because I can see the discovery messages still happening. But nothing is happening when a smartapp uses the device (who’s job it is to send a text announcement message to the Raspberry Pi for converting text to speech and playing through my speakers).

Any thoughts?

** My device handler…**
metadata {
definition (name:“Watson Announcer”, namespace:“toddaustin07”, author:“Todd Austin”) {

    capability "Audio Notification"
    capability "Switch Level"

    // Custom attributes
    attribute "connection", "string"
    attribute "output", "string"
    attribute "volume", "number"
    attribute "voice", "string"

    // Custom commands
    command "setOutput", ["string"]
    command "setVolume", ["number"]
    command "setVoice", ["string"]
    command "testTTS"
    command "setRangedLevel", ["number"]
    
}

tiles(scale:2) {

    multiAttributeTile(name:"connectionTile", type:"generic", width:6, height:3) {
		tileAttribute("device.connection", key: "PRIMARY_CONTROL") {
            attributeState "connected", label:'${currentValue}', backgroundColor:"#44b621", icon:"http://toddaustin07.github.io/Icons/Watson_Pictogram.png"
            attributeState "disconnected", label:'${currentValue}', backgroundColor:"#d04e00", icon:"st.custom.sonos.muted"
        }				              
       
        tileAttribute("inuseStatus", key: "SECONDARY_CONTROL") {
        
            attributeState "val", label:'Status: ${currentValue}'
                    
        }
    }
    
    valueTile("volumeTile", "device.rangedLevel", width:3, height:2) {
        state "range", label:'dB: ${currentValue}', defaultState: true
    }
    
    controlTile("levelSliderControl", "device.rangedLevel", "slider", width:3, height:2, range:"(-6000..0)") {
        state "level", action:"setRangedLevel"
    }
    
    standardTile("selectOutputTile", "device.output", width:2, height:2) {
        state "local", label: 'Local', action: "setOutput", backgroundColor: "#00a0dc", icon:"st.Electronics.electronics6", defaultState:true, nextState:"alsa"
        state "alsa", label: 'Alsa', action: "setOutput", backgroundColor:"#20d2a7", icon:"st.Electronics.electronics16", nextState:"local"
    }
    
    standardTile("selectVoiceTile", "device.voice", width:2, height:2) {
        state "en-GB_KateVoice", label: 'Kate', action: "setVoice", backgroundColor: "#ffc0cb", icon:"st.People.people7", nextState:"en-US_AllisonVoice"
        state "en-US_AllisonVoice", label: 'Allison', action: "setVoice", backgroundColor: "#ffc0cb", icon:"st.People.people6", nextState:"en-US_LisaVoice"
        state "en-US_LisaVoice", label: 'Lisa', action: "setVoice", backgroundColor: "#ffc0cb", icon:"st.People.people11", nextState:"en-US_MichaelVoice"
        state "en-US_MichaelVoice", label: 'Michael', action: "setVoice", backgroundColor: "#add8e6", icon:"st.People.people14", nextState:"en-GB_KateVoice"
    }
    
    standardTile("testTTS", "device.test", width:2, height:2, inactiveLabel:false, decoration:"flat") {
        state "default", label:"Test", icon:"st.alarm.beep.beep", action:"testTTS"
    }
    
    main("connectionTile")
    details(["connectionTile","statusTile","volumeTile", "levelSliderControl", "selectOutputTile", "selectVoiceTile", "testTTS"])
}

}

def installed() {

log.debug "INSTALLING DEVICE HANDLER"
sendEvent([name:'output', value:'local', displayed:false])
state.output = 'local'
sendEvent([name:'connection', value:'connected', displayed:false])
sendEvent([name:"inuseStatus", value:"idle"])
sendEvent([name:'voice', value:'en-US_MichaelVoice', displayed:false])

}

def updated() {
sendEvent([name:“inuseStatus”, value:“idle”])
log.debug “UPDATED DEVICE HANDLER”
}

// parse events into attributes
def parse(description) {

def retstatus = "idle"
def msg = parseLanMessage(description)

def headersAsString = msg.header // => headers as a string
def headerMap = msg.headers      // => headers as a Map
def body = msg.body              // => request body as a string
def status = msg.status          // => http status code of the response
def json = msg.json              // => any JSON included in response body, as a data structure of lists and maps
def xml = msg.xml                // => any XML included in response body, as a document tree structure
def data = msg.data              // => either JSON or XML in response body (whichever is specified by content-type header in response)

log.debug "Watson Announcer return status: ${status}"

if (status != 200) {
    retstatus = "${status} error"
}

def evnt1 = createEvent([name:"inuseStatus", value:retstatus])

return(evnt1)

}

def playText(text,audiolevel) {

log.debug "playText command has been called with msg ='${text}', and level = ${audiolevel}"
def volume = 0

sendEvent([name:"inuseStatus", value:"playing"])

if (audiolevel == null) {
    volume = state.level
} 
else {
    volume = (6000 - (audiolevel * 60)) * -1
}
log.debug "Computed volume = ${volume}"
return doAction("PlayMessage", "WatsonTTS", "./", [MessageText: text, SpeakingVoice: state.voice, AudioOutput: state.output, Volume: volume])

}

def setOutput(audioOutput) {

log.debug "setOutput input parm = ${audioOutput}"

if (audioOutput == 'local' || audioOutput == 'alsa') {
    sendEvent([name:'output', value:audioOutput])
    state.output = audioOutput
}

}

def setRangedLevel(value) {
log.debug "Setting ranged level to $value"
sendEvent([name:“rangedLevel”, value:value, displayed:false])
sendEvent([name:‘volume’, value:value])
state.volume = value
}

def setVolume(audioVolume) {

log.debug "Set volume request = ${audioVolume}"

if (audioVolume > 0 && audioVolume <= 100) {
    volume = (6000 - (audioVolume * 60)) * -1
    
} else if (audioVolume <= 0) {
    volume = audioVolume
}

sendEvent([name:'volume', value:volume])
sendEvent([name:'rangedLevel', value:volume, displayed:false])
state.volume = volume

log.debug "Volume set to ${volume}"

}

def setVoice(audioVoice) {
log.debug “Set voice request = ${audioVoice}”

if (audioVoice != null) {
    if (audioVoice == "en-GB_KateVoice" ||
        audioVoice == "en-US_AllisonVoice" ||
        audioVoice == "en-US_LisaVoice" ||
        audioVoice == "en-US_MichaelVoice") {
        sendEvent([name:"voice", value: audioVoice])
        state.voice = audioVoice
    }

} else {

    state.voice = device.currentValue('voice')

    if (state.voice == "en-GB_KateVoice") {
        sendEvent([name:"voice",value:"en-US_AllisonVoice"])
        state.voice = "en-US_AllisonVoice"
    } else if (state.voice == "en-US_AllisonVoice") {
        sendEvent([name:"voice",value:"en-US_LisaVoice"]) 
        state.voice = "en-US_LisaVoice"
    } else if (state.voice == "en-US_LisaVoice") {
        sendEvent([name:"voice",value:"en-US_MichaelVoice"]) 
        state.voice = "en-US_MichaelVoice"
    } else if (state.voice == "en-US_MichaelVoice") {
        sendEvent([name:"voice",value:"en-GB_KateVoice"]) 
        state.voice = "en-GB_KateVoice"
    } else {
        sendEvent([name:"voice",value:"en-US_MichaelVoice"]) 
        state.voice = "en-US_MichaelVoice"    
    }
}

}

def testTTS() {
log.debug "Test requested"
sendEvent([name:“inuseStatus”, value:“playing”])
state.status = "playing"
return doAction(“PlayMessage”, “WatsonTTS”, “./”, [MessageText: “This is a Smart things test”, SpeakingVoice: state.voice, AudioOutput: state.output, Volume: state.volume])
}

def sync(ip, port) {
def existingIp = getDataValue(“ip”)
def existingPort = getDataValue(“port”)
if (ip && ip != existingIp) {
updateDataValue(“ip”, ip)
}
if (port && port != existingPort) {
updateDataValue(“port”, port)
}
}

def doAction(action, service, path, Map body = [InstanceID:0]) {

def result = new physicalgraph.device.HubSoapAction(
    path:    path,
    urn:     "urn:schemas-upnp-org:service:$service:1.0",
    action:  action,
    body:    body,
    headers: [Host:getHostAddress(), CONNECTION: "close"]
)
return result

}

private getCallBackAddress() {
return device.hub.getDataValue(“localIP”) + “:” + device.hub.getDataValue(“localSrvPortTCP”)
}

// gets the address of the device
private getHostAddress() {
def ip = getDataValue(“ip”)
def port = getDataValue(“port”)

if (!ip || !port) {
    def parts = device.deviceNetworkId.split(":")
    if (parts.length == 2) {
        ip = parts[0]
        port = parts[1]
    } else {
        log.warn "Can't figure out ip and port for device: ${device.id}"
    }
}

log.debug "Using IP: ${convertHexToIP(ip)} and port: ${convertHexToInt(port)} for device: ${device.id}"
return convertHexToIP(ip) + ":" + convertHexToInt(port)

}

private Integer convertHexToInt(hex) {
return Integer.parseInt(hex,16)
}

private String convertHexToIP(hex) {
return [convertHexToInt(hex[0…1]),convertHexToInt(hex[2…3]),convertHexToInt(hex[4…5]),convertHexToInt(hex[6…7])].join(".")
}

What happens when you manually create a new device and assign it this DTH without any service manager referencing it? Basically just like a dumb virtual device. When you open that device in the mobile app, does it crash as well?

Are all your tile attributes initialized? We have faced this issue in the past when the mobile app tries to render a multiattributetile with an attribute value of null or undefined it used to crash. The solution was to make sure all attributes are defined.

I found the source of my problem: It looks like ST changed the slider control and it broke my device handler. They have a bug in the platform that doesn’t like slider ranges that go from a negative number to 0. I’ll report this to ST.

Thank you for the suggestions, gentlemen.

1 Like