Squeezebox Audio Alert Script

Hi, any chance to do a config that is not an app for hass.io since im running the virtual environment version?

So after at bit trial and error I got thisā€¦wellā€¦some what workingā€¦or well, like once, then I got the message below.

At first it played my mp3, once, but it didnā€™t restore my playlistā€¦

Any ideas?

JSONRPC=http://user:[email protected]:9000/jsonrpc.js
input={ā€œplayer_nameā€: ā€œK\u00f6ketā€, ā€œmacā€: ā€œ00:c8:2b:00:7d:c6ā€, ā€œalert_mp3ā€: ā€œ//hdd//storage01//Doorbell.mp3ā€, ā€œalert_volumeā€: 100}
[Info] Read player_name: Kƶket
[Info] Read mac: 8c:c8:2b:00:7d:c6
[Info] Read alert_mp3: //hdd//storage01//incoming//Doorbell.mp3
[Info] Read tts_message: null
[Info] Read alert_volume: 100
restore_playlist=1
script is lockedā€¦exiting.

Can someone point me in the right direction to do this in bash? I looked at using jq like this:
JSON="$(curl --silent -X GET -H "Content-Type: application/json" -d '{"id":1,"method":"slim.request","params":["'"MAC"'",["status","-"]]}' http://SERVERIP:9000/jsonrpc.js)"
power=jq -r '.result.power' $JSON
but I canā€™t figure out how to do it without saving the json string to a file or multiple calls to LMS.
power=jq -r '.result.power' json.txt
works when json.txt is a file that jq can read from.

I was not paying attention. It is truly trivial:

JSON="$(curl --silent -X GET -H "Content-Type: application/json" -d '{"id":1,"method":"slim.request","params":["'"MAC"'",["status","-"]]}' http://SERVERIP:9000/jsonrpc.js)"
power=$(echo $JSON | jq -r '.result.power')
name=$(echo $JSON | jq -r '.result.player_name')
etc....

Thanks a lot for the work! I updated script for shell usage (not HAss.IO component) form post 41 (I guess)ā€¦
I had two issues:

  • no ā€œjqā€ - didnt want to install anything extra, so I updated the scripts using ā€œsedā€, which should be available without extra installation
  • I didnt see the need for the ā€œplaylist_nameā€, so I generated the name out of the MAC-address, which would need to be provided to make all work.

If somebody is interested, here we go:

#!/bin/bash

#squeezebox_alert_save_playlist.sh

#NOTE: Edit "user", "pass", ip and port below
#JSONRPC="http://user:[email protected]:9000/jsonrpc.js"
JSONRPC="http://192.168.20.201:9001/jsonrpc.js"

#Example command arguments
#mac="00:04:20:01:02:03"
#alert_volume=60

mac=$1
alert_volume=$2
playlist_name=$(echo "$1" | sed 's/\://g')

#get power state
power=$(curl -X POST -H "Content-Type: application/json" \
                  -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["power","?"]]}' \
                  $JSONRPC | sed -n 's/.*"_power"\:"\([^"]*\)".*/\1/p')

prev_power=0
if [[ $power =~ .*1.* ]] ; then
  prev_power=1 
fi
echo "prev_power=$prev_power"

#get play mode
mode=$(curl -X POST -H "Content-Type: application/json" \
                 -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["mode","?"]]}' \
                 $JSONRPC | sed -n 's/.*"_mode"\:"\([^"]*\)".*/\1/p')

prev_playmode=0
if [[ $mode =~ .*play.* ]] ; then
  prev_playmode=1
fi	 
echo "prev_playmode=$prev_playmode"

noplay=1
prev_time=0
if [ $prev_playmode -eq 1 ] ; then
  noplay=0
  
  # pause currently playing song
  curl -X POST -H "Content-Type: application/json" \
       -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["pause"]]}' $JSONRPC
  echo "pause currently playing song"

  # get paused time
  prev_time=$(curl -X POST -H "Content-Type: application/json" \
                   -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["time","?"]]}' \
                   $JSONRPC | sed -n 's/.*"_time"\:\([0-9\.]*\).*/\1/p')
  echo "prev_time=$prev_time"
fi

# save current playlist
curl -X POST -H "Content-Type: application/json" \
     -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["playlist","save","'"$playlist_name"'","silent:1"]]}' $JSONRPC
echo "save current playlist"

# GET SETTINGS TO RESTORE AFTER PLAYING ALERT SONG
#get current volume 
prev_volume=$(curl -X POST -H "Content-Type: application/json" \
                   -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["mixer","volume","?"]]}' \
                   $JSONRPC | sed -n 's/.*"_volume"\:"\([^"]*\)".*/\1/p')
echo "prev_volume=$prev_volume"

#get current repeat setting
prev_repeat=$(curl -X POST -H "Content-Type: application/json" \
                   -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["playlist","repeat","?"]]}' \
                   $JSONRPC | sed -n 's/.*"_repeat"\:"\([^"]*\)".*/\1/p')
echo "prev_repeat=$prev_repeat"

#write settings to file
DIRECTORY=$(cd `dirname $0` && pwd)
echo "Save settings to file: $DIRECTORY/$playlist_name.txt"
> "$DIRECTORY/$playlist_name.txt"
printf '%s\n%s\n%s\n%s\n%s\n' $prev_power $prev_playmode $prev_time $prev_volume $prev_repeat > "$DIRECTORY//$playlist_name.txt"

# SET SETTINGS FOR ALERT SONG
#set alert_volume to command argument value
curl -X POST -H "Content-Type: application/json" \
     -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["mixer","volume",'$alert_volume']]}' $JSONRPC
echo "set alert_volume"

#set repeat setting to 0
curl -X POST -H "Content-Type: application/json" \
     -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["playlist","repeat",0]]}' $JSONRPC
echo "set repeat_setting to 0"

And the restore part:

#!/bin/bash

#squeezebox_alert_restore_playlist.sh

#NOTE: Edit "user", "pass", ip and port below
#JSONRPC="http://user:[email protected]:9000/jsonrpc.js"
JSONRPC="http://192.168.20.201:9001/jsonrpc.js"

#Example command arguments
#mac="00:04:20:01:02:03"

mac=$1
playlist_name=$(echo "$1" | sed 's/\://g')

# WAIT FOR ALERT SONG TO STOP PLAYING
echo "wait for alert song to stop playing"
cur_mode="play"
while [[ $cur_mode =~ .*play.* ]]; do
  sleep 1
  cur_mode=$(curl -X GET -H "Content-Type: application/json" \
              -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["mode","?"]]}' \
               $JSONRPC | sed -n 's/.*"_mode"\:"\([^"]*\)".*/\1/p')
done

echo "alert song stopped playing"

# read settings from file
DIRECTORY=$(cd `dirname $0` && pwd)
echo "Restore settings from file: $DIRECTORY/$playlist_name.txt"
IFS=$'\n' read -d '' -r -a lines < "$DIRECTORY/$playlist_name.txt"
prev_power="${lines[0]}"
prev_playmode="${lines[1]}"
prev_time="${lines[2]}"
prev_volume="${lines[3]}"
prev_repeat="${lines[4]}"
echo "prev_power=$prev_power"
echo "prev_playmode=$prev_playmode"
echo "prev_time=$prev_time"
echo "prev_volume=$prev_volume"
echo "prev_repeat=$prev_repeat"

noplay=1
if [ $prev_playmode -eq 1 ] ; then
  noplay=0
fi

# RESTORE PREVIOUS SETTINGS
#restore prev_volume setting
if [ ! -z $prev_volume ] ; then
  curl -X POST -H "Content-Type: application/json" \
     -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["mixer","volume",'"$prev_volume"']]}' $JSONRPC
  echo "restore prev_volume setting"
fi

#restore prev_repeat setting
if [ ! -z $prev_repeat ] ; then
  curl -X POST -H "Content-Type: application/json" \
     -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["playlist","repeat",'"$prev_repeat"']]}' $JSONRPC
  echo "restore prev_repeat setting"
fi

# resume previous playlist
curl -X POST -H "Content-Type: application/json" \
     -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["playlist","resume","'"$playlist_name"'","noplay:'$noplay'"]]}' $JSONRPC
echo "resume previous playlist"

# RESUME PREVIOUSLY PLAYING MUSIC
if [ $prev_playmode -eq 1 ] ; then
  if [ ! -z $prev_time ] ; then
    #skip ahead in song to prev_time
    curl -X POST -H "Content-Type: application/json" \
       -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["time",'"$prev_time"']]}' $JSONRPC
    echo "skip ahead in song to prev_time"
  fi
fi

#restore prev_power setting
if [ $prev_power -eq 0 ] ; then
  echo "prev_power setting was off, power off"
  curl -X POST -H "Content-Type: application/json" \
       -d '{"id":1,"method":"slim.request","params":["'"$mac"'",["power",0]]}' $JSONRPC
fi
2 Likes

On remark for users using above scripts: I noticed that everything fails, if you try to address more than one squeezebox which are grouped. E.g. dormitory and bathroom are a group, I cannot use both entities for playback, I just have to name one.
-> For now this is a known issue. I dont plan to work around it for now.

Nice this is really cool, how did I not see this before? Iā€™m actually trying to use NodeRED for Squeezebox automation and this is far beyond what Iā€™m currently able to do.

Can I assume this all works without HAā€™s squeezebox component:

Iā€™ve had to stop using HAā€™s squeezebox component as is doesnā€™t handle players leaving and joining the network at all! It throws an error every 10 seconds if I power off a player.

Finally, LMS also supports sending lines of text to the displays of squeezebox players, Iā€™ve tried a few test messages, it may be nice to have alert/message appear on screen also. :slight_smile:

Itā€™s easy to miss a voice message if there is other sounds around, or you are in another room.

I dont use the squeezebox component. The control script above just uses the JSONRPC from the LMS. But I need the squeezeboxes to be integrated as mediaplayers. With HA they are discovered automatically and this allows an easy use with the tts.google service or simila.
I was planning to build a message queue as currently the latest announcment wins and it will announce also when I am outsideā€¦ :-(. I had no time to dig into this (I am not a programmer).

currently I use a script to define each alert playback, which is triggered from a python_script. This is an example:

play_goodmorningmessage_guest:
  alias: "Nachricht: Guten Morgen"
  sequence:
    - service: tts.google_say
      data_template:
        entity_id: "{{ media_players }}"
        message: >
          {% if now().strftime("%H")|int < 12 %}
            Guten Morgen.
          {% elif now().strftime("%H")|int < 18 %}
            Guten Tag.
          {% else %}
            Guten Abend.
          {% endif %}
            Liebe GƤste.
            Wir haben {{states.sensor.dark_sky_temperature.state|round}} Grad in unserer Stadt. Das Wetter:
          {% if 'Morgen' not in states('sensor.dark_sky_hourly_summary') %}
            {% if 'heute' not in states('sensor.dark_sky_hourly_summary') %}
              {% if 'Heute' not in states('sensor.dark_sky_hourly_summary') %}
                Heute
              {% endif %}
            {% endif %}
          {% endif %}
          {{states.sensor.dark_sky_hourly_summary.state|replace(".", "")}}. 

This is an automation which plays back the message under certain conditions (only in the morning and only once per day). It triggers when the light in th ekitchen is turned on or the radio is starting playback in a specific time window:

- alias: Guten Morgen
  trigger:
    - platform: state
      entity_id: switch.licht_kuche_tresen
      to: 'on'
    - platform: state
      entity_id: media_player.squeezebox_kueche
      to: 'playing'
  condition:
    condition: and
    conditions:
      - condition: time
        before: '09:00:00'
        after:  '06:45:00'
      - condition: state
        entity_id: input_boolean.var_goodmorningmessage_done
        state: 'off'
  action:
    - delay: '00:00:05'
    - service: input_boolean.turn_on
      data:
        entity_id: input_boolean.var_goodmorningmessage_done
    - service: python_script.make_announcement
      data:
        entities: 
          - media_player.squeezebox_kueche:60
          - media_player.squeezebox_bad:45
        message: script.play_goodmorningmessage

- alias: Reset Good morning message played variable
  trigger:
    - platform: time
      at: '00:00:00'
  action:
    - service: input_boolean.turn_off
      data:
        entity_id: input_boolean.var_goodmorningmessage_done

for each message, I create a script as above (play_goodmorningmessage_guest). It takes as a variable the entityID of the media_player(s). Then I have a python script, which takes care of announcement. I couldnt get this done in automations as I couldnt playback to multiple media_players at the same time:

##########################################################################################
# Script to handle different announcements based on data provided. Needs to be placed under "python_scripts" folder
# ##########################################################################################
# set target squeezebox mediaplayer, volume and announcemtn script in the yaml file calling the script
#  automation example:
#    alias: announce something
#    sequence:
#      - service: python_script.make_announcement
#        data:
#          media_players:
#            - media_player.squeezebox_kueche:31    # entity_id:volume_for_announcement (0-100)
#            - media_player.squeezebox_az:40        # entity_id:volume_for_announcement
#          tts: script.announcement                 # entity_id
#
##########################################################################################

        
def save_mediaplayer_stat(mediaplayers,mac_address,volume,hass):
    for entity in mediaplayers:
        service_data = { 'mac': mac_address[entity], 'volume': volume[entity] }
        hass.services.call('shell_command', 'playlist_save_sb', service_data, True)
    return

def restore_mediaplayer_stat(mediaplayers,mac_address,hass):
    for entity in mediaplayers:
        service_data = { 'mac': mac_address[entity] }
        hass.services.call('shell_command', 'playlist_restore_sb', service_data, True)
    return

def play_announcement(mediaplayers,tts,hass):
    for entity in mediaplayers:
        service_data = { 'media_players': entity }
        hass.services.call('script', tts.split('.')[1], service_data, True)
    return

#######
# Main
#######

entities = []
mediaplayers = []
defaultvolume = 60   # 0-100

logger.error("Test: defaulvolume ".format(defaultvolume))

if isinstance(data.get('entities'), str):
    entities.append(data.get('entities'))
    logger.error("Received string as {}".format(entities))
else:
    entities.extend(data.get('entities'))
    logger.error("Received list as {}".format(entities))
#else:
#    logger.error("Trying to announce something, but no target media player provided!")
#    quit()

tts = data.get("message", "")
    
if tts in [None, ''] or len(entities) == 0:
    logger.error("Trying to announce something, but no announcement script or target mediaplayer provided!")
#    quit() 

# internal variables
mac_address = {
    'media_player.squeezebox_kueche': '##:##:##:##:##:##',
    'media_player.squeezebox_wz': '##:##:##:##:##:##',
    'media_player.squeezebox_bad': '##:##:##:##:##:##',
    'media_player.squeezebox_gastbad': '##:##:##:##:##:##',
    'media_player.squeezebox_az': '##:##:##:##:##:##',
    'media_player.squeezebox_kz': '##:##:##:##:##:##',
    'media_player.squeezebox_sz': '##:##:##:##:##:##',
}
volume = {
    'media_player.squeezebox_kueche': defaultvolume,
    'media_player.squeezebox_wz': defaultvolume,
    'media_player.squeezebox_bad': defaultvolume,
    'media_player.squeezebox_gastbad': defaultvolume,
    'media_player.squeezebox_az': defaultvolume,
    'media_player.squeezebox_kz': defaultvolume,
    'media_player.squeezebox_sz': defaultvolume,
}

# get target volume and mediaplayer entities
for item in entities:
    tmp_list = item.split(':')
    if len(tmp_list)>1 and tmp_list[1].isdigit() and 0<=int(tmp_list[1])<=100:
        volume[tmp_list[0]] = tmp_list[1]
    mediaplayers.append(tmp_list[0])

logger.info("Making announcement to the following {} mediaplayer(s):".format(len(mediaplayers)))
for entity in mediaplayers:
    logger.info("  - {}".format(entity) + " , volume: {}".format(volume[entity]))
logger.info("Preparing to announce {}".format(tts))


save_mediaplayer_stat(mediaplayers,mac_address,volume,hass)
play_announcement(mediaplayers,tts,hass)
restore_mediaplayer_stat(mediaplayers,mac_address,hass)

This the configuration.yaml lines:

shell_command:
  playlist_save_sb:    bash /config/bash/squeezebox_alert_save_playlist.sh "{{mac}}" {{volume}}
  playlist_restore_sb: bash /config/bash/squeezebox_alert_restore_playlist.sh "{{mac}}"

Summary what this does:

  • there are 2 bash script which are storing and restoring the state of the media_player in question (configuration.yaml, above posts) -> some smart person can put this also into the python script I guess
  • there is a python script which handles the playback to multiple entities and triggers restore and save based on the media_players handed over and the target volume for the alert (this post)
  • there is a script for each alert text (I did this outside python to easily add new alerts or announcements by creating new scripts and using the script name for playback - I wanted to have content separated from execution)
  • the automations call the python script and handover the target media_player(s), target alert_volume and the announcement.

Sounds a bit complicated I guess. You should be able to put the bash script logic into the python script, but I simply reused what I had and avoided extra work.
And - as written above - the python script doesnt queue messages. I would like to get a message queue, which is played back when getting back home.

I hope this helps

Could you explain how you get the queezeboxes as mediaplayers into HA when you are not using the squeezebox component?

In details I dont know. At least I have not configured the Squeezebox component in configuration.yaml.
The component allows direct integration via the REST API of the Squeezebox if I recall correctly. This function I dont use.
I use the mini-media player (or you can use the standard media_player) component. This allows me to control play/stop/on/off, but nothing more. I didnt want to do song selection etc via HA - for this I use iPeng as specific software. I am using HA only to turn the boxes on/off and announce alerts via TTS.

As stated above: the squeezeboxes have been discovered automatically without the SB component in configuration.yaml.

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