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