TTS being called differently in Button Controller 5.1 vs. Notifications

I am moving a button rule for my Inovelli Switch from Rule Machine 4.0 to Button Controllers. One of my multi taps scense confirms the event by announcing the activity using LANnouncer. I just noticed that the methods called in LANnouncer is differenet based on the Hubitat application where it is configured.

In Button Controller 5.1 it calls a "Toast" method for TTS:
image

From the device event log:

And throws the following error:

2022-06-04 01:01:15.545 pm [error](http://hubitat-home-c7.vargofamily.com/device/edit/202)groovy.lang.MissingMethodException: No signature of method: user_driver_RonV42_LANnouncer_Alerter_Hubitat_494.speak() is applicable for argument types: (java.lang.String, null) values: [Living Room Programmatic On, null]

But when using TTS in Notifications everything works fine and it calling the Speak method:

The device event:

The question is why would one built in app use the speak method and another built in app use the "Toast" method? This may be a bug in the Button Controller since TOAST would be a text or SMS method vs a TTS method. I am using a slightly modified LANnouncer driver that I modified to replace spaces in the string to be web service call safe.

/**
 *  LANnouncer Alerter-Hubitat (Originally LANdroid Alerter)
 *
 *  Requires the Android TTSService (aka LANnouncer  in the Play Store; https://play.google.com/store/apps/details?id=com.keybounce.lannouncer )
 *
 *  Copyright 2015 Tony McNamara
 *  8/1/2019 - Further modifyed by Ron Vargo to remove SmartThings references and support spaces in TTS strings
 *
 *  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: "LANnouncer Alerter-Hubitat", namespace: "RonV42", author: "Ron Vargo") {
        capability "Alarm"
        capability "Speech Synthesis"
        capability "Notification"
        capability "Tone"
        attribute  "LANnouncerSMS","string"
    }
    preferences {
        input("DeviceLocalLan", "string", title:"Android IP Address", description:"Please enter your tablet's I.P. address", defaultValue:"" , required: false, displayDuringSetup: true)
        input("DevicePort", "string", title:"Android Port", description:"Port the Android device listens on", defaultValue:"1035", required: false, displayDuringSetup: true)
        input("ReplyOnEmpty", "bool", title:"Say Nothing", description:"When no speech is found, announce LANdroid?  (Needed for the speech and notify tiles to work)", defaultValue: true, displayDuringSetup: true)
    }

    simulator {
        
    }

    tiles {
        standardTile("alarm", "device.alarm", width: 2, height: 2) {
            state "off", label:'off', action:'alarm.both', icon:"st.alarm.alarm.alarm", backgroundColor:"#ffffff"
            state "strobe", label:'strobe!', action:'alarm.off', icon:"st.Lighting.light11", backgroundColor:"#e86d13"
            state "siren", label:'siren!', action:'alarm.off', icon:"st.alarm.alarm.alarm", backgroundColor:"#e86d13"
            state "both", label:'alarm!', action:'alarm.off', icon:"st.alarm.alarm.alarm", backgroundColor:"#e86d13"
        }

        standardTile("strobe", "device.alarm", inactiveLabel: false, decoration: "flat") {
            state "default", label:'', action:"alarm.strobe", icon:"st.secondary.strobe"
        }
        standardTile("siren", "device.alarm", inactiveLabel: false, decoration: "flat") {
            state "default", label:'', action:"alarm.siren", icon:"st.secondary.siren"
        }       

        standardTile("speak", "device.speech", inactiveLabel: false, decoration: "flat") {
            state "default", label:'Speak', action:"Speech Synthesis.speak", icon:"st.Electronics.electronics13"
        }
        standardTile("toast", "device.notification", inactiveLabel: false, decoration: "flat") {
            state "default", label:'Notify', action:"notification.deviceNotification", icon:"st.Kids.kids1"
        }
        standardTile("beep", "device.tone", inactiveLabel: false, decoration: "flat") {
            state "default", label:'Tone', action:"tone.beep", icon:"st.Entertainment.entertainment2"
        }       

        main "alarm"
        details(["alarm","strobe","siren","speak","toast","beep"])
    }
}

// parse events into attributes
def parse(String description) {
    log.debug "Parsing '${description}'"
    // TODO: handle 'alarm' attribute

}

// handle commands
def off() {
    log.debug "Executing 'off'"
    // TODO: handle 'off' command
}

def strobe() {
    log.debug "Executing 'strobe'"
    // TODO: handle 'strobe' command
    def command="&FLASH=STROBE&"+getDoneString()
    sendCommands(command)

}

def siren() {
    log.debug "Executing 'siren'"
    // TODO: handle 'siren' command
    def command="&ALARM=SIREN&"+getDoneString()
    sendCommands(command)
}

def beep() {
    log.debug "Executing 'beep'"
    // TODO: handle 'siren' command
    def command="&ALARM=CHIME&"+getDoneString()
    sendCommands(command)
}

def both() {
    log.debug "Executing 'both'"
    // TODO: handle 'both' command
    def command="&ALARM=ON&FLASH=ON&"+getDoneString()
    sendCommands(command)
}


def speak(toSay) {
    log.debug "Executing 'speak'"
    if (!toSay?.trim()) {
        if (ReplyOnEmpty) {
            toSay = "Landroid Speech Synthesizer"
        }
    }
    
    toSay = toSay.replaceAll("\\s","%20")

    if (toSay?.trim()) {
        def command="&SPEAK="+toSay+"&"+getDoneString()
        sendCommands(command)
    }
}

def deviceNotification(toToast) {
    log.debug "Executing notification with "+toToast
    if (!toToast?.trim()) {
        if (ReplyOnEmpty) {
            toToast = "Landroid Speech Synthesizer"
        }
    }

     toToast = toToast.replaceAll("\\s","%20")   
    
    if (toToast?.trim()) {
        def command="&TOAST="+toToast+"&"+getDoneString()
        sendCommands(command)
    }
}    

/* Send to IP and to SMS as appropriate */
private sendCommands(command) {
    log.debug "Command request: "+command
//    sendSMSCommand(command)
    sendIPCommand(command)
}

private sendIPCommand(commandString ) {
    log.debug "Sending command to "+DeviceLocalLan
    sendEvent(name: "LANnouncerIP", value: commandString, isStateChange: true)
    if (DeviceLocalLan?.trim()) {
        def host = DeviceLocalLan 
        def hosthex = convertIPtoHex(host)
        def porthex = convertPortToHex(DevicePort)
        device.deviceNetworkId = "$hosthex:$porthex" 

        def headers = [:] 
        headers.put("HOST", "$host:$DevicePort")

        def method = "GET"

        def hubAction = new hubitat.device.HubAction([
            method: method,
            path: "/"+commandString,
            headers: headers],
            device.deviceNetworkId
            )
        hubAction
    }
}

private sendSMSCommand(commandString) {
    def preface = "+@TTSSMS@+"
    def smsValue = preface+"&"+commandString
    state.lastsmscommand = smsValue
    sendEvent(name: "LANnouncerSMS", value: smsValue, isStateChange: true)
    /*
    if (SMSPhone?.trim()) {
        sendSmsMessage(SMSPhone, preface+"&"+commandString)
    }
    */
}

private String getDoneString() {
    return "@DONE@"
}


private String convertIPtoHex(ipAddress) { 
    String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02x', it.toInteger() ) }.join()
    log.debug "IP address entered is $ipAddress and the converted hex code is $hex"
    return hex

}

private String convertPortToHex(port) {
    String hexport = port.toString().format( '%04x', port.toInteger() )
    log.debug hexport
    return hexport
}

Both apps use speak(). The difference is that Notifier uses speak(msg) and Button Rule uses speak(msg, volume).

You simply need to modify the speak() method in the driver so that it is

def speak(toSay, volume = null) {

1 Like

Thank you modified the code last night and works great. I am back in business again with some of my button event announcements. I should have seen that in the error message but my tired eyes aren't what they used to be when it comes to objects and code.