[UPDATED] - Message Central - Make your home speak

Message Central. (Parent & Child)

This is port of my ST app with a few changes & additions

As this is an initial port I’ll call it a beta :slight_smile:

I have tested most of the functions but not all scenarios.

PushOver requires a new virtual device using the excellent driver here:

Join Messaging requires a new virtual device using the excellent driver here:

You can select various inputs and actions so that the system will ‘announce’ events.


Switch - On/Off

Contact - Open/Close

Contact - Left open too long

Presence - Arrival/Departure

Water - Wet/Dry

Time - Schedule a reminder at configured time

Time - if contact open - I use this to check the shed/garage door is closed at night

Power - Alert when power goes below or above a threshold
(And optionally ‘stays that way’ for a configurable amount of time - perfect for laundry appliances that drop below the threshold a couple of times before finally staying there when finished)

Appliance Power Monitor - This works the same as the power monitoring except that the power drawn MUST go above a threshold before monitoring starts

Motion - on ‘active’ or on 'inactive’

Temperature - either above or below a set point.

Mode Change


Weather Alert - from a suitable device


  • Voice Message (MusicPlayer)
  • Voice Message (SpeechSynth)
  • SMS Message
  • Pushover Message
  • Join Message
  • Play an MP3

All of these can be restricted by mode, presence, time, ‘enable/disable’ virtual switch(es) and days of the week.

Added a second presence sensor restriction – You can restrict by two.
1 at home and another away or 2 at home/away

Debug logging is switchable for each child so once tested can be switched off to reduce clutter in the logs

I added individual enable/disable switches so you can disable a single announcement temporarily without having to delete the child app or silence the whole system.

I have added configurable delays both before and after messages to help prevent truncation and to stop repeated messages within a number of minutes. (useful for ‘door’ announcements when the break-beam or pressure mat is repeatedly triggered or laundry announcements every 1/2 hour if nobody turns the appliance off)

I have included a number of optional variables you can use in either voice or text messages

The following variables can be used in your event messages and will be replaced with the details listed below

%time% - Replaced with current time in 12 or 24 hour format (Switchable)

%day% - Replaced with current day of the week

%date% - Replaced with the date

%year% - Replaced with the year

%greeting% - Replaced with 'Good Morning', 'Good Afternoon' or 'Good Evening' (evening starts at 6pm)

%group1% - Replaced with the a random message from Group1

%group2% - Replaced with the a random message from Group2

%group3% - Replaced with the a random message from Group3

%group4% - Replaced with the a random message from Group4

%opencontact% - Replaced with a list of configured contacts if they are open

%closedcontact% - Replaced with a list of configured contacts if they are closed

%opencount% - Replaced with the number of configured contacts that are open

%closedcount% - Replaced with the number of configured contacts that are closed

%lightsOn% - Replaced with the list of configured lights that are on

%lightsOncount% - Replaced with the number of configured lights that are on

%lightsOff% - Replaced with the list of configured lights that are off

%lightsOffcount% - Replaced with the number of configured lights that are off

%switchesOn% - Replaced with the list of configured switches that are on

%switchesOncount% - Replaced with the number of configured switches that are on

%switchesOff% - Replaced with the list of configured switches that are off

%switchesOffcount% - Replaced with the number of configured switches that are off

%device% - Replaced with the name of the triggering device

%event% - Replaced with what triggered the action (e.g. On/Off, Wet/Dry)

%mode% - Replaced with the current hub location mode

This is a ‘Parent/Child’ app so both files will need to be added to your Hubitat hub.

You’ll find the files here:
Parent: 2.2.0
Child: 12.7.1



  1. Copy the Parent code from GitHub into a 'New App' under the 'Apps Code' menu then click 'Save'
  2. Copy the Child code from GitHub into a second 'New App' & save this too
  3. Go to 'Apps'
  4. Click 'Load New Apps'
  5. Select 'Message Central' under 'User Apps'
  6. Save
  7. Go back to Apps and open Message Central - You can now create new child apps directly from here.

Install and save the Parent first before creating any children.

Important Note:

Please bear in mind when using ‘Speak A Message’ that there is a limit of 128 characters for TTS

If you have longer messages they will simply not work!

You need to remember this when configuring the group random messages

If you want to try and find some bugs for me please do!

Updated: 25/10/2018

Parent: 2.2.0
Child: 12.7.1

Parent Updates:
Added to 'Cobra Apps' Container

Child Updates:

  • V12.7.1 - Edited variables help page to show new variables available
  • V12.7.0 - Added app pause switch
  • V12.6.0 - Added a bunch of new variables so you can report on light & switch state
  • V12.5.1 - Change the way %rain% is spoken - If 0% then: 'Rain is not expected today" - If n% then 'There is a n% chance of rain today
  • V12.5.0 - Added an optional, configurable 'prefix' input for weather alerts - "Message to play before weather alert (optional)"

Updated: 16/10/2018

Added a new trigger 'Weather Alert'
This will take the output from a suitable weather device and speak/message the alert
This will only activate if there is a change in the alert so should activate whenever a new alert is sent by the device
Added a new trigger: 'Lock/Unlock'
(Thanks to @cwwilson08 for all his help in testing this and help debugging Mp3 issues)
This version also includes a switch to enable/disable the 'poll' of the weather device for weather attributes other than 'Alert'

This version also has a number of 'bugfixes' included

  • Fixed Mp3 playback
  • Fixed %time% variable where it would say "0 pm/am" instead of 'o'clock' on the hour
  • Fixed issues where a lock trigger would not send a 'Join' message

Updated: 27/09/2018

Added a couple more variables for use in messages & debug mp3 playing

Updated: 05/09/2018

I have added a new 'feature' of the number of selectable random messages you can have in each group
I have also increased the groups to 4

A big 'Thank You!' to @matthew for his creative work on the code for number of random phrases Without him, you would not have this feature!

Updated 31/08/2018
I have now given you the option to use a 'speech synthesis' device in addition to the previous 'music player' device

One thing to note:
For anyone who has been using this with the 'musicPlayer' capability
You will need to open each child and select "Voice Message (MusicPlayer)" as the old "Voice Message" is no longer valid.
Once selected the previous settings should reappear so you can then just save the child again.

I have also added a number of 'weather' variables.
The following variables have been added:

%wsum% - Replaced with a weather summary
%high% - Replaced with a 'Forecast High'
%low% - Replaced with a 'Forecast Low
%hum% - Replaced with a 'Humidity'
%wnow% - Replaced with 'Weather Now'
%rain% - Replaced with 'Chance of Rain'
%vis% - Replaced with 'Visibility'
%wgust% - Replaced with 'Wind Gust'
%wspeed% - Replaced with 'Wind Speed'
%wdir% - Replaced with 'Wind Direction'
%feel% - Replaced with 'FeelsLike'
%temp% - Replaced with 'Temperature'

You will need to configure the appropriate 'weather device' for these to work.




this is the ST code I was using to get date & day

private getDay(){
	def df = new java.text.SimpleDateFormat("EEEE")
	if (location.timeZone) {
	else {
	def day = df.format(new Date())
    return day

private parseDate(date, epoch, type){
    def parseDate = ""
    if (epoch){
    	long longDate = Long.valueOf(epoch).longValue()
        parseDate = new Date(longDate).format("yyyy-MM-dd'T'HH:mm:ss.SSSZ", location.timeZone)
    else {
    	parseDate = date
    new Date().parse("yyyy-MM-dd'T'HH:mm:ss.SSSZ", parseDate).format("${type}", timeZone(parseDate))
private getdate() {
    def month = parseDate("", now(), "MMMM")
    def dayNum = parseDate("", now(), "dd")
  LOGDEBUG("Date:  $dayNum $month")
    LOGDEBUG("dayNum = $dayNum - Converting into 'proper' English")
    if(dayNum == "01"){dayNum = dayNum.replace("01","THE FIRST OF")}
	if(dayNum == "02"){dayNum = dayNum.replace("02","THE SECOND OF")}
    if(dayNum == "03"){dayNum = dayNum.replace("03","THE THIRD OF")}
    if(dayNum == "04"){dayNum = dayNum.replace("04","THE FOURTH OF")}
    if(dayNum == "05"){dayNum = dayNum.replace("05","THE FIFTH OF")}
    if(dayNum == "06"){dayNum = dayNum.replace("06","THE SIXTH OF")}
    if(dayNum == "07"){dayNum = dayNum.replace("07","THE SEVENTH OF")}
    if(dayNum == "08"){dayNum = dayNum.replace("08","THE EIGHTH OF")}
    if(dayNum == "09"){dayNum = dayNum.replace("09","THE NINTH OF")}
    if(dayNum == "10"){dayNum = dayNum.replace("10","THE TENTH OF")}
    if(dayNum == "11"){dayNum = dayNum.replace("11","THE ELEVENTH OF")}
    if(dayNum == "12"){dayNum = dayNum.replace("12","THE TWELTH OF")}
    if(dayNum == "13"){dayNum = dayNum.replace("13","THE THIRTEENTH OF")}
    if(dayNum == "14"){dayNum = dayNum.replace("14","THE FOURTEENTH OF")}
    if(dayNum == "15"){dayNum = dayNum.replace("15","THE FIFTEENTH OF")}
    if(dayNum == "16"){dayNum = dayNum.replace("16","THE SIXTEENTH OF")}
    if(dayNum == "17"){dayNum = dayNum.replace("17","THE SEVENTEENTH OF")}
    if(dayNum == "18"){dayNum = dayNum.replace("18","THE EIGHTEENTH OF")}
    if(dayNum == "19"){dayNum = dayNum.replace("19","THE NINETEENTH OF")}
    if(dayNum == "20"){dayNum = dayNum.replace("20","THE TWENTIETH OF")}
    if(dayNum == "21"){dayNum = dayNum.replace("21","THE TWENTY FIRST OF")}
    if(dayNum == "22"){dayNum = dayNum.replace("22","THE TWENTY SECOND OF")} 
    if(dayNum == "23"){dayNum = dayNum.replace("23","THE TWENTY THIRD OF")}
    if(dayNum == "24"){dayNum = dayNum.replace("24","THE TWENTY FOURTH OF")}
    if(dayNum == "25"){dayNum = dayNum.replace("21","THE TWENTY FIFTH OF")}
    if(dayNum == "26"){dayNum = dayNum.replace("26","THE TWENTY SIXTH OF")}
    if(dayNum == "27"){dayNum = dayNum.replace("27","THE TWENTY SEVENTH OF")}
    if(dayNum == "28"){dayNum = dayNum.replace("28","THE TWENTY EIGHTH OF")}
    if(dayNum == "29"){dayNum = dayNum.replace("29","THE TWENTY NINTH OF")}
    if(dayNum == "30"){dayNum = dayNum.replace("30","THE THIRTIETH OF")}
    if(dayNum == "31"){dayNum = dayNum.replace("21","THE THIRTY FIRST OF")}
     LOGDEBUG("Day number has been converted to: '$dayNum'")  
    return dayNum + " " + month + " "
private getyear() {
    def year = parseDate("", now(), "yyyy")
   LOGDEBUG("Year =  $year")
    return year

It's just not working and reads out the variable name
Where am I making a stupid mistake?? :slight_smile:

Pushover notifications are in all "CAPS" regardless of how they're setup, is this by design? Minor quibble, just an FYI.

Will this work with join notification code that was posted? The possibilities of additional automation through tasker are endless...

Are you using any variables?

Not sure what you mean

I was thinking of this...


I can't tell where the string's being put together for the call to textToSpeech
I would start debugging there, if the variable name is being read, then the variable isn't being inserted into the string...
var text = "time of day is variable" vs "time of day is ${variable}"

It actually gets converted to uppercase so that the variables can be matched.
But I think it does it for everything.
I’ll have a look at this if it is a problem for you.


Got ya, nah don't worry about it. Just thought it was strange, but I understand why now.

I just had a chance to look at the driver.
this driver uses "Speech Synthesis" as a capability so is not compatible with MC as this was written for capability.musicPlayer


This driver should function almost exactly as the Pushover driver. I'm not sure what, if any, changes the Hubitat team made when they imported @ogiewon's version, but it should work essentially the same. If not, I'm sure the Join driver can be easily edited to do so. Let me know the specific command you execute on compatible drivers and I can add a method to handle the request.

1 Like



MC uses playTextAndRestore(state.fullPhrase) and it uses capability.musicPlayer to detect & select the speaker

I noticed the driver uses the command speak() to process the text
Perhaps if the playTextAndRestore() command was mapped to the speak() command that would do it


I just realised
for PushOver I am using the speak()
Let me look at this to see if I can do something


1 Like

Andy, I haven't looked at your MC code, but shouldn't it just work? They both have the same methods and capabilities. Your "input" should not know the difference...correct?

1 Like

To make the driver accessible/selectable for 'input' it will need the capability "Music Player"
But otherwise I would think it would work


Maybe @bravenel added that capability. I will add it to the join driver and see if that works...and saves you unnecessary effort.

It's strange but I've noticed the version of @ogiewon's driver I'm using has the capability but not the one from the link in the 1st post.
Hmm.. wonder where I got that from? :slight_smile:


Maybe I need to rethink the way MC calls it anyway