Squeezebox Audio Alert Script

Discovery might have happened due to the fact that I installed a UPNP plugin into my squeezebox server…
I dont encounter any stability/disconnection issues with this setup.
I also played around more with the REST API of the server instead of the bash scripts using curl. I use this to playback favorite music by setting volume and a preset, sending the mac address and preset (1-6) from the automation through a data_template:

rest_command:
  lms_play_preset:
    url: "http://LMS-IP/status.html?player={{mac}}&p0=button&p1=playPreset_{{preset}}"
  lms_set_volume:
    url: "http://LMS-IP/status.html?player={{mac}}&p0=mixer&p1=volume&p2={{volume}}"
1 Like

Hello,

Are you still using this as a workaround? I am looking something very similiar.

I would like to announce ready made tts files when certain conditions are met and continue the playlist afterwards. I am using hassio and squeezebox component. My players are always grouped and synced and I control them with volume.

Thanks!

@smazman first of all thanks for the effort and your support. I deployed your add-on in hass.io and got the automation running. Everything is working for playing an MP3 file on the LMS, but i can’t get Google TTS working.
The script is executing and the actual music is stopped, then i get a silence and then the playlist is restored. So everything works except the TTS part. When i use TTS directly to the player it also work (but kills the playlist). I replaced API_TOKEN by ${HASSIO_TOKEN} as mentioned in your post and also added -sS to the Curl command to have cleaner log output.
This is the output of the log:

[Info] Read player_name: squeezebox_wit
[Info] Read mac: 00:04:20:2a:97:49
[Info] Read alert_mp3: null
[Info] Read tts_message: Testing. Testing. 1, 2, 3.
[Info] Read alert_volume: 55
restore_playlist=1
prev_power=1
prev_playmode=1
pause currently playing song
{"method":"slim.request","id":1,"params":["00:04:20:2a:97:49",["pause"]],"result":{}}prev_time=232.336445877075
save current playlist
{"method":"slim.request","id":1,"params":["00:04:20:2a:97:49",["playlist","save","squeezebox_wit","silent:1"]],"result":{"__playlist_id":9830}}prev_volume="25"
prev_repeat="0"
set ALERT_VOLUME
{"params":["00:04:20:2a:97:49",["mixer","volume","55"]],"result":{},"id":1,"method":"slim.request"}set repeat_setting to 0
{"params":["00:04:20:2a:97:49",["playlist","repeat","0"]],"result":{},"method":"slim.request","id":1}play TTS alert
400: Bad Requestwait for alert song to stop playing
alert song stopped playing
restore prev_volume setting
{"result":{},"params":["00:04:20:2a:97:49",["mixer","volume","25"]],"id":1,"method":"slim.request"}restore prev_repeat setting
{"method":"slim.request","id":1,"params":["00:04:20:2a:97:49",["playlist","repeat","0"]],"result":{}}resume previous playlist
{"id":1,"method":"slim.request","result":{},"params":["00:04:20:2a:97:49",["playlist","resume","squeezebox_wit","noplay:0"]]}skip ahead in song to prev_time

My automation script:

  action:
  - service: hassio.addon_stdin
    data:
      addon: local_squeezebox_alert
      input:
        player_name: "squeezebox_wit"
        mac: "00:04:20:2a:97:49"
        # alert_mp3: "/volume1/music/Tones/Doorbell-chimes-melody.mp3"
        tts_message: "Testing. Testing. 1, 2, 3."
        alert_volume: 55

Log when using MP3 (which is working):

input={"player_name": "squeezebox_wit", "mac": "00:04:20:2a:97:49", "alert_mp3": "/volume1/music/Tones/Doorbell-chimes-melody.mp3", "alert_volume": 55}
[Info] Read player_name: squeezebox_wit
[Info] Read mac: 00:04:20:2a:97:49
[Info] Read alert_mp3: /volume1/music/Tones/Doorbell-chimes-melody.mp3
[Info] Read tts_message: null
[Info] Read alert_volume: 55
restore_playlist=1
prev_power=1
prev_playmode=1
pause currently playing song
{"params":["00:04:20:2a:97:49",["pause"]],"result":{},"method":"slim.request","id":1}prev_time=136.169120107651
save current playlist
{"method":"slim.request","id":1,"params":["00:04:20:2a:97:49",["playlist","save","squeezebox_wit","silent:1"]],"result":{"__playlist_id":9830}}prev_volume="25"
prev_repeat="0"
set ALERT_VOLUME
{"params":["00:04:20:2a:97:49",["mixer","volume","55"]],"result":{},"method":"slim.request","id":1}set repeat_setting to 0
{"method":"slim.request","id":1,"params":["00:04:20:2a:97:49",["playlist","repeat","0"]],"result":{}}play alert mp3
{"method":"slim.request","id":1,"params":["00:04:20:2a:97:49",["playlist","play","/volume1/music/Tones/Doorbell-chimes-melody.mp3"]],"result":{}}wait for alert song to stop playing
alert song stopped playing
restore prev_volume setting
{"id":1,"method":"slim.request","params":["00:04:20:2a:97:49",["mixer","volume","25"]],"result":{}}restore prev_repeat setting
{"method":"slim.request","id":1,"result":{},"params":["00:04:20:2a:97:49",["playlist","repeat","0"]]}resume previous playlist
{"params":["00:04:20:2a:97:49",["playlist","resume","squeezebox_wit","noplay:0"]],"result":{},"id":1,"method":"slim.request"}skip ahead in song to prev_time

Any idea what can be wrong ? I see a 400 Bad request…

Hi,

nice one but you should be aware of users / players that matches one ot both circumstands

  • fade in/out is used
  • dontstopthemusic is used

i would change this part from:

to:

   # WAIT FOR ALERT SONG TO STOP PLAYING
   echo "wait for alert song to stop playing"
   sleep 1
   wait=$(curl -X GET -H "Content-Type: application/json" \
               -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["duration","?"]]}' \
               $JSONRPC| jq '.result._duration')
   wait=$(echo $wait -1|bc)
   sleep $wait

cheers

and another one :wink:

i dont know the reason why you only delete the alarm when the player was in the status play?
I would move the part

# delete alert song from playlist

out of the

# RESUME PREVIOUSLY PLAYING MUSIC
elif [[ $prev_mode =~ .*play.* ]] ; then

into the part

# RESTORE PREVIOUS SETTINGS

otherwise someone presses play at anytime later and the alarm starts :frowning:

And (just for dontstopthemusic users)

if [ $num_tracks -eq 0 ]
    then
    echo playlist was empty
    curl --silent -X GET -H "Content-Type: application/json" \
         -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["playlist","clear"]]}' $JSONRPC
fi

cheers

Hi all,

I’ve made some enhancements to the great work @smazman has done.

I’ve been using this for about a year now, and it’ been working quite well. A few times I found that my SqueezeBox players ended up in a loop where they were playing the alert over and over again. Also, I found that when resuming playback after an alert, the playback would skip back to the start of the song, rather than to the position it was playing just before the alert.

In order to ‘fix’ these issues, I had a go at enhancing the script. Below is the updated version of squeezebox_alert.sh:

#!/bin/bash

# squeezebox_alert.sh

JSONRPC=$1
PLAYER_NAME=$2
MAC=$3
ALERT_MP3=$4
TTS_MSG=$5
ALERT_VOLUME=$6

ALERT_TIMEOUT=10 # seconds

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
LOCK_FILE_DIR=$SCRIPT_DIR/locks
LOCK_FILE=$LOCK_FILE_DIR/$PLAYER_NAME.lock

##################################################################################################
# Initialization
##################################################################################################

current_timestamp=$(date +%s)

mkdir -p $LOCK_FILE_DIR

# Exit script if a lock file named $PLAYER_NAME exists, which signifies this player is being used
if [ -f $LOCK_FILE ] ; then
  lock_timestamp=$(cat $LOCK_FILE)
  echo "Previous lock file exists (" $lock_timestamp ")"

  lock_duration="$(($current_timestamp - $lock_timestamp))"

  # Delete the existing lock file if the timeout as been reached
  if [ $lock_duration -gt $ALERT_TIMEOUT ] ; then
    echo "Lock file is stale... deleting"
    rm $LOCK_FILE
  else
    echo "Script is locked... exiting"
    exit 0
  fi
fi

# Create a lock file named $PLAYER_NAME to signify this player is being used
echo $current_timestamp >> $LOCK_FILE

##################################################################################################
# Get original player state
##################################################################################################

# Get original player status
echo -ne "Getting original player status... "
status=$(curl -s -X POST -H "Content-Type: application/json" \
              -d '{ "method": "slim.request", "params": ["'"$MAC"'", ["status", "-" ]] }' \
              $JSONRPC | jq '.result')
echo "done"

echo -ne "    Power state: "
prev_power=`echo $status | jq '.power'`
echo $prev_power

echo -ne "    Play mode: "
prev_playmode=`echo $status | jq -r '.mode'`
echo $prev_playmode

echo -ne "    Mixer volume: "
prev_volume=`echo $status | jq '."mixer volume"'`
echo $prev_volume

echo -ne "    Playlist repeat: "
prev_repeat=`echo $status | jq '."playlist repeat"'`
echo $prev_repeat

echo -ne "    Playlist shuffle: "
prev_shuffle=`echo $status | jq '."playlist shuffle"'`
echo $prev_shuffle

echo -ne "    Song time: "
prev_time=`echo $status | jq '.time'`
echo $prev_time

##################################################################################################
# Pause original song / save original playlist
##################################################################################################

noplay=1
if [ $prev_playmode == "play" ] ; then
  noplay=0

  playmode=$prev_playmode
  pause_attempt=0
  while [[ $playmode != "pause" ]] && [[ $pause_attempt -lt 20 ]]; do
    pause_attempt=$(($pause_attempt + 1))

    # Pause original song
    echo -ne "Pausing original song... "
    curl -s -X POST -H "Content-Type: application/json" \
         -d '{ "method": "slim.request", "params": ["'"$MAC"'", ["pause"]] }' $JSONRPC > /dev/null

    sleep 0.5

    playmode=$(curl -s -X POST -H "Content-Type: application/json" \
                    -d '{"method": "slim.request", "params": ["'"$MAC"'", ["mode", "?"]] }' \
                    $JSONRPC | jq -r '.result._mode')

    if [ $playmode == "pause" ]; then
        echo "paused"
    else
        echo "still playing"
    fi
  done
fi

# Save original playlist
echo -ne "Saving original playlist... "
curl -s -X POST -H "Content-Type: application/json" \
     -d '{"method": "slim.request", "params": ["'"$MAC"'", ["playlist", "save", "'"$PLAYER_NAME"'", "silent:1" ]] }' $JSONRPC > /dev/null
echo "saved"

##################################################################################################
# Play alert
##################################################################################################

# Set mixer volume
echo -ne "Setting alert mixer volume... "
curl -s -X POST -H "Content-Type: application/json" \
     -d '{"method": "slim.request", "params": ["'"$MAC"'", ["mixer", "volume", '$ALERT_VOLUME']] }' $JSONRPC > /dev/null
echo $ALERT_VOLUME

# Set playlist repeat to 0
echo -ne "Setting alert playlist repeat... "
curl -s -X POST -H "Content-Type: application/json" \
     -d '{"method": "slim.request", "params": ["'"$MAC"'", ["playlist", "repeat", 0]] }' $JSONRPC > /dev/null
echo 0

# If ALERT_MP3 is null, play alert using TTS
if [ $ALERT_MP3 = "null" ] ; then
  echo -ne "Playing alert TTS... "
  curl -s -X POST -H "x-ha-access: API_TOKEN" -H "Content-Type: application/json" \
       -d '{"entity_id": "media_player.'$PLAYER_NAME'", "message": "'"$TTS_MSG"'"}' \
       http://http://192.168.1.251:8123/homeassistant/api/services/tts/google_say > /dev/null
  echo "playing"
else
  # Play alert mp3
  echo -ne "Playing alert MP3... "
  curl -s -X POST -H "Content-Type: application/json" \
       -d '{"method": "slim.request", "params": ["'"$MAC"'", ["playlist", "play", "'"$ALERT_MP3"'"]] }' $JSONRPC > /dev/null
  echo "playing"
fi

# Wait for alert to stop playing
cur_mode="play"
play_timestamp=$(date +%s)
play_wait_duration=0
while [[ $cur_mode == "play" ]] && [[ $play_wait_duration -lt $ALERT_TIMEOUT ]]; do
  echo -ne "Waiting for alert to stop playing... "

  sleep 0.5

  cur_mode=$(curl -s -X POST -H "Content-Type: application/json" \
              -d '{"method": "slim.request", "params": ["'"$MAC"'", ["mode", "?"]] }' \
              $JSONRPC | jq -r '.result._mode')
  play_wait_duration="$(((`date +%s`) - $play_timestamp))"

  if [ $cur_mode == "play" ]; then
    echo "still playing"
  else
    echo "stopped"
  fi
done

##################################################################################################
# Restore original player state
##################################################################################################

# Restore original playlist repeat
echo -ne "Restoring original playlist repeat ... "
curl -s -X POST -H "Content-Type: application/json" \
     -d '{"method": "slim.request", "params": ["'"$MAC"'", ["playlist", "repeat", '"$prev_repeat"']] }' $JSONRPC > /dev/null
echo $prev_repeat

# Restore original playlist shuffle
echo -ne "Restoring original playlist shuffle ... "
curl -s -X POST -H "Content-Type: application/json" \
     -d '{"method": "slim.request", "params": ["'"$MAC"'", ["playlist", "shuffle", '"$prev_shuffle"']] }' $JSONRPC > /dev/null
echo $prev_shuffle

# Restore original playlist
echo -ne "Restoring original playlist... "
curl -s -X POST -H "Content-Type: application/json" \
     -d '{"method": "slim.request", "params": ["'"$MAC"'", ["playlist", "resume", "'"$PLAYER_NAME"'", "noplay:1", "wipePlaylist:1"]] }' $JSONRPC > /dev/null
echo $PLAYER_NAME

if [ $prev_playmode == "play" ] ; then
  # Setting mixer volume to 0 (to allow silently skipping to original song play time)
  echo -ne "Setting mixer volume... "
  curl -s -X POST -H "Content-Type: application/json" \
       -d '{"method": "slim.request", "params": ["'"$MAC"'", ["mixer", "volume", "0"]] }' $JSONRPC > /dev/null
  echo 0

  # Play original song
  echo -ne "Playing original song... "
  curl -s -X POST -H "Content-Type: application/json" \
       -d '{ "method": "slim.request", "params": ["'"$MAC"'", ["play"]] }' $JSONRPC > /dev/null

  cur_mode=""
  play_timestamp=$(date +%s)
  play_wait_duration=0
  while [[ $cur_mode != "play" ]] && [[ $play_wait_duration -lt $ALERT_TIMEOUT ]]; do
    echo -ne "Waiting for original song to start playing... "

    cur_mode=$(curl -s -X POST -H "Content-Type: application/json" \
                    -d '{"method": "slim.request", "params": ["'"$MAC"'", ["mode", "?"]] }' \
                    $JSONRPC | jq -r '.result._mode')
    play_wait_duration="$(((`date +%s`) - $play_timestamp))"

    if [ $cur_mode == "play" ]; then
      echo "playing"
    else
      echo "not yet playing"
    fi
  done

  sleep 2 # allow some playback before skipping to original song play time

  # Restore original song play time
  echo -ne "Skipping to original song play time... "
  curl -s -X POST -H "Content-Type: application/json" \
       -d '{"method": "slim.request", "params": ["'"$MAC"'", ["time", '"$prev_time"']] }' $JSONRPC > /dev/null
  echo $prev_time
fi

# Restore original mixer volume
echo -ne "Restoring original mixer volume... "
curl -s -X POST -H "Content-Type: application/json" \
     -d '{"method": "slim.request", "params": ["'"$MAC"'", ["mixer", "volume", '"$prev_volume"']] }' $JSONRPC > /dev/null
echo $prev_volume

# Restore original power state
if [ $prev_power -eq 0 ] ; then
  echo -ne "Restoring original power state... "
  curl -s -X POST -H "Content-Type: application/json" \
       -d '{"method": "slim.request", "params": ["'"$MAC"'", ["power", 0]] }' $JSONRPC > /dev/null
  echo $prev_power
fi

# Wait a few seconds and then delete lock file (this seems to be necessary to allow curl commands to complete)
#echo -ne "Waiting 2 seconds... "
#sleep 2
#echo "done"

##################################################################################################
# Clean up
##################################################################################################

echo -ne "Deleting lock file... "
rm $LOCK_FILE
echo "deleted"

echo "Script completed"
exit 0

I originally had this working as the Hass.io addon, but with my new version, it seemed like the script could not be run in parallel, in the scenario where you have multiple speakers and want them all to announce the alert at the same time.

I ended up implementing this in vanilla HA scripts. Below is an example:

automations.yaml

- id: '1605907262247'
  alias: Announce doorbell press on all speakers
  trigger:
    platform: state
    entity_id: binary_sensor.front_door_ding
    to: "on"
  action:
  - service: script.turn_on
    entity_id: script.announce_on_all_speakers
    data:
      variables:
        alert_mp3: "http://HOMEASSISTANT/local/mp3/ring_chime.mp3"
  mode: single

scripts.yaml

announce_on_all_speakers:
  alias: Announce on all speakers
  sequence:
  - service: script.turn_on
    entity_id: script.announce_on_speaker
    data:
      variables:
        player_name: "Kitchen"
        mac: "xx:xx:xx:xx:xx:xx"
        alert_mp3: "{{ alert_mp3 }}"
        alert_volume: 70
  - service: script.turn_on
    entity_id: script.announce_on_speaker
    data:
      variables:
        player_name: "LivingRoom"
        mac: "xx:xx:xx:xx:xx:xx"
        alert_mp3: "{{ alert_mp3 }}"
        alert_volume: 50

announce_on_speaker:
  alias: Announce on speaker
  sequence:
  - service: shell_command.announce_on_speaker
    data:
      player_name: "{{ player_name }}"
      mac: "{{ mac }}"
      alert_mp3: "{{ alert_mp3 }}"
      alert_volume: "{{ alert_volume }}"
  mode: parallel
  max: 10

shell_commands.yaml

shell_command:
  announce_on_speaker: '/config/shell_commands/squeezebox_alert.sh "http://LOGITECH_MEDIA_SERVER:9000/jsonrpc.js" "{{ player_name }}" "{{ mac }}" "{{ alert_mp3 }}" null {{ alert_volume }} &'

As you can see above, the announce_on_speaker script supports parallel mode, which allows all my speakers to play the alerts at the same time.

If anyone is willing to try this updated script, let me know how it goes.

BTW, slightly off topic but related, has anyone else run into probems trying to play TTS on the Squeezebox players? For me, it shows the URL in the player but nothing is played. For some of the MP3 files I have, I had to change the bit rate for them to actually work. I used this online tool:

https://online-audio-converter.com/

I simply uploaded each MP3 file, and clicked Convert (using default of 128 kbps). After that,the MP3 files were able to play on my Squeezebox players.

Chers,
Petar

4 Likes

Very interested in testing your script but I am a bit lost

How should I proceed. Do I copy the squeezebox_alert.sh on all my squeezebox player ?

Hi @ppmt,

The squeezebox_alert.sh script only needs to exist on your Home Assistant machine. You can put it anywhere beneath the main HA config folder. For example, mine is stored here:

{HOME_ASSISTANT_FOLDER}/shell_commands/squeezebox_alert.sh

Cheers,
Petar

1 Like

Thanks for your answer. I will try it today

After a lot of trial and error I finally got your script to work.

The only issue I have is that the playlist is not restarting. I can see that the playlist for each player is created but it doesn’t reload it at the end

This is already quite good for me so will continue to check what/if I am missing something

@ppmt Can you try using the version that @smazman posted back in post 65? Just to see if that one works and your playlists are resumed?

Thanks for the help @pkozul

I tried the script you mentioned and it is the same behaviour. Here is what happens as I see it

I start playing an album on my player then I trigger the automation
It saves what is playing in a playlist and then play the alert
After that instead of reloading the playlist it replaces it with nothing

If I check in the LMS it just says empty…but the playlist is definitely present in the playlist section

I have solved my issue.

It was a permission problem on the _Playlist directory inside LMS!
After fixing it I can now trigger an alarm and it will resume exactly where it was before it

This script is perfect for me. Thanks @pkozul and @smazman for that script.

2 Likes

@ppmt Great to hear Philippe. Can you just temporarily use the original script (post #65) just to see whether that script successfully resumes the playlist exactly where it was paused?

I just want to see whether my new version actually makes a difference (for someone else other than myself!).

So I tried the said script and at the beginning I thought that it was working the same as yours. It was restarting at the same time it was interrupted but then I realised that it was not the same song.
After triggering the script a few time it seems that it restart at the same time but using the next song in the playlist.
Very strange

1 Like

I found a great plugin for lms called lmsannounce. It give you an api to do announcements to your squeezebox-player.
Your announcement can be a playlist, a file (mp3, flac ect.) or a TTS. The cool thing is it saves whatever your playing en resumes after the announcement is done!
I’ve used it to extend my doorbell which triggers a shell command doorbell.

doorbell: 'curl -k "http://192.168.1.1:9000/plugins/LMSannounce/js.html?cmd=announcePlaylist&playlist=Doorbell&playerid=b1%3a21%3aeb%3acf%3a33%3a47"'

Works great for me!

june 17th 2022:
I now use this intergration by floris-b wich is even better!

3 Likes

Thank you very much for this finding!

1 Like

Awesome find!

I’ve combined it with my earlier efforts to make a easy (or easier) to deploy TTS addon… (using the other method in this topic earlier)
Also because I don’t like the TTS options lmsannouce gives if made it so that you can use whatever TTS home assistant uses.

I’m not much of a programmer so lots of BETA flags and <insert i have no idea what i’m doing dog in lab image here>

Add the repo, install addon, configure the lmsserver:port (without http://) and use.

Using call service → hassio.addon_stdin

input:
  alert_volume: 100
  mac: '00:00:00:00:00:00'
  base_dir: '/volume1/apps/tts/'
  tts_message: 'Hi, i am a TTS notification. May i interrupt?'

where base dir is where the tts cache directory is mounted.
Did I mention you need to mount the home assistant tts cache on your LMS server?

Well this was easier in my head… Also I just realized i did not implement playing existing files quite yet.
Look, I wanted TTS, got all excited it worked and wanted to post! Will update later with a better guide and update to addon to include playing non TTS.

3 Likes

Hi PhiAX,

Wanting to use your script, but have not got it working yet. Can you share me details/examples on the call service? And the mounting done on the LMS server?

Thanks!

Sorry it took awhile but here it is:

First install and enable LMSannounce on your LMS server.
Then make sure the Samba add-on is installed and configured on your Home Assistant.

Mount the TTS cache folder from your home assistant config share on your LMS server. Mine is a synology.


image

Install my add-on and configure it with the url to LMS with the portnumber included.(like: mediaserver.local:9002, or 192.168.1.100:9000)

Then try to call the service in the developer tools or a automation like this:

The addon part is the slugs name for the addon. I don’t know if this is the same for everyone or anything but you can look it up by looking at the url when you’ve selected the addon in the addon store.

Where base_dir is the path to the mounted folder from earlier, as seen by your LMS server. The others are self explanatory.

That should be it.