Anyway to make voice announcements local?

I’m currently using the chromecast integration for voice announcements, But when my internet goes out I don’t get the announcements, is there anyway I can make them local without need for the internet? even if I need to get new equipment to achieve this that’s fine just need to know if it can be done. Thanks in advance for any help.

1 Like

I'm looking into Sonos speakers for basically the same reason. Might be a thought. I'm not up to speed on it, but that seems to be the solution to keep it local.

From the research I’ve done that’s what I’m hearing, I already have a Sonos setup a beam and 2 1s we use it now with our tv setup when I tried to use it for announcements it caused havoc and my wife WAS NOT HAPPY with the outcome so I was able to find a Chromecast audio and started using that. The Sonos are just so damn expensive just to use for announcements.

Well you can do what I did and use a old Android phone and install and configure Lannouncer app as a server. The old phone has a headphone jack which I connected into my intercom system looking like it's just another station that is set to a broadcast station. I then use a older device driver that was converted from SmartThings for Lannouncer and put in the phones ip address and the port Lannouncer was configured to listen on. I don't use the SMS option since I run this phone without a sim card.

It's been working for about two years now with really no issues unless the phone disconnects and doesn't reconnect to the wifi network in my home but that only happened once.

/**
 *  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"
        }
    }
    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
}
2 Likes

There is always the Ikea version that is much cheaper.

1 Like

Big thx ronv42 I’ll give that a go

You could also get a DLNA-capable speaker such as the Fabriq Riff and use MediaRenderer or Rules to have it play local sound files saved to your Hubitat.

I heard we could now save mp3s to our hubs do we have to be on a certain software number

I think the File Manager has been out got quite some time. Settings : Show advanced options (On) : File Manager.

2 Likes

Ok Thanks a bunch

I have a couple Eufy speakers. I used an app called media renderer to make them into Sonos knock-offs. They can be used to play music and controlled by Hubitat at this point. They're not great, but what they do very well is play a pre-recorded message. I generated a bunch of voice clips using an AWS service, I think it was Amazon Poly. Stored them on my hub and call the Eufy speakers as music device to play back my clips. I have a dozen announcements plus some door chime sound effects I use on holidays.

2 Likes

Nice! That's a great idea to render the clips through Amazon beforehand. I used a GlaDOS voice generator and some autotune effects in Melodyne to render a bunch of clips for my scenes and routines. Plus a few "official" sound clips from Portal ("Hello, and again, welcome to the Aperture Science Enrichment Center" / "Who turned out the lights?" / turrets saying "He-E-ello" / etc.).

1 Like

Which is exactly what Hubitat’s built-in TTS engine does. For each new text phrase, Hubitat makes one call to AWS’s Poly service, and then caches the resulting audio file on the hub. The next time that exact phrase is requested, there is no need to hit AWS, and the hub will simply reuse the cached audio clip, and send it to the Sonos speaker.

3 Likes

Interesting. Is there a reason the built-in TTS voices are such poor quality, compared to the standard “Alexa” / Echo Speaks voice?

They are simply the AWS Polly voices. They seem alright to me.

2 Likes

I don't know if they are "such poor quality", but I would agree that they are less natural than the standard "Alexa" voice.

Agreed. Except I think that a subset of the AWS Polly voices support "Neural TTS", in which the use of audio spectrograms and re-coding is supposed to make more "human-like" voices. The standard Alexa voice is a Neural TTS voice.

The voice engine used can be selected at the time of generation. I don't know whether Hubitat uses neural TTS for those voices that support it.

2 Likes

The AWS polly pricing is 4x more expensive for the neural voices. I wouldn't expect Hubitat to use them since they have to eat the cost. I set the hubitat voice to Joanna and find it good for my purposes.

2 Likes

I purchased a few aeotec doorbells, then used a TTS website to create voice files to load to them. I don't remember what site I used, but a google search for text to speech converter should provide you with something. You may need to try a few to find a voice that sounds natural to you.

I numbered the files so I would always load them to the doorbell in the same order. By doing this, I can set the same "chime" number for all of the doorbells and have the correct file play on all of them.

The only problem I've found is that one doorbell sometimes stutters. My only guess is that it gets confused when I'm sending the command to all three doorbells in the house.

I have never messed with any of this on Sonos/Chromecast but if it's only a handful of the same announcements I wonder if you could just store/send an audio clip of the announcement via something like base64.

I use something similar for sending audio alerts to the HomePod mini in my son's room whenever his laundry is done... although those are just vintage video game sound effects.

I have 3 Sonos speakers and love them. The builtin integration in Hubitat works well. No cloud required..... Beside that they sound great!

1 Like