Page 20 of 21

Re: Logitech Media Server

Posted: Tuesday 01 February 2022 13:09
by Masterscot
Hi,

I use the second variant (sync groups ) of the Perl script on: https://www.domoticz.com/wiki/Logitech_Media_Server
Had to use the JSON Legacy Client version.
LMS is running at a different machine then Domoticz.

Everything works ok, except when I switch one of 3 squeezebox players I use, to off.
Then the script fails with:
malformed JSON string, neither tag, array, object, number, string or atom, at character offset 0 (before "Server closed connec...") at /usr/local/share/perl/5.30.0/JSON/RPC/Legacy/Client.pm line 180.

caused by this Line:
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['playlist', 'path', '?'] ] });
(at the beginning of the sub squeezeAlert)

Any ideas? I have basic programming skill, but not familiar with Perl.

Re: Logitech Media Server

Posted: Tuesday 01 February 2022 15:54
by philchillbill
Masterscot wrote:Hi,

I use the second variant (sync groups ) of the Perl script on: https://www.domoticz.com/wiki/Logitech_Media_Server
Had to use the JSON Legacy Client version.
LMS is running at a different machine then Domoticz.

Everything works ok, except when I switch one of 3 squeezebox players I use, to off.
Then the script fails with:
malformed JSON string, neither tag, array, object, number, string or atom, at character offset 0 (before "Server closed connec...") at /usr/local/share/perl/5.30.0/JSON/RPC/Legacy/Client.pm line 180.

caused by this Line:
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['playlist', 'path', '?'] ] });
(at the beginning of the sub squeezeAlert)

Any ideas? I have basic programming skill, but not familiar with Perl.
The jsonrpc interface in LMS does not gracefully handle errors. It just bombs out. What would you like to have it do when a player is off? Which begs the question, why do you even turn it off at all?

Re: Logitech Media Server

Posted: Tuesday 01 February 2022 16:36
by Masterscot
Thanks for your reply.
I can't change the fact that some of the players I have will be switched off at some moment in time.
What would be ideal is to test the @players array first for which players are online according to LMS.
Then (re)fill the @players array with the switched-on players and then continue.
This could be done in a foreach loop at the top of the the squeezeAlert sub, but I need a way to capture an error or response from the LMS if given a player's ID, instead of bombing out. Since the LMS 'knows' which player is online. Is there maybe a way to get a list with online players from it?

Re: Logitech Media Server

Posted: Tuesday 01 February 2022 16:53
by philchillbill
The query ["serverstatus", 0, 64] will give you a lot of useful information about LMS.

Re: Logitech Media Server

Posted: Tuesday 01 February 2022 19:27
by Masterscot
Thanks for the pointer, I can work with that.

Are the RPC commands available the same as explained in the LMS webpages technical information -> Command Line Interface?

Re: Logitech Media Server

Posted: Tuesday 01 February 2022 19:46
by philchillbill
Yes. :mrgreen:

When I wrote the original scripts long ago, I was a heavy Perl user but these days I do everything in nodejs. If I had to do it again I'd rewrite for that because my Perl is now quite rusty. But I have a lot of experience with jsonrpc.js because I wrote the MediaServer Alexa skill for LMS. If you have any questions just shoot.

Re: Logitech Media Server

Posted: Tuesday 01 February 2022 20:17
by Masterscot
First of all, thank you for the scripts! They are excellent examples to work from.
I use them with Domoticz to play announcements in the house for certain events. Like door bell, garage door opens/closes, etc.
Very useful.
I thought I had a solution with the <playerid> connected ? command.
This gives back a 1 or 0 if LMS sees a player is connected or not.
Unfortunately this works only within the first couple of minutes a player is disconnected.
After that time I get the error back again. So maybe there is something at LMS not working as it should.
Have to try to make something work with the <serverstatus>.

Re: Logitech Media Server

Posted: Tuesday 01 February 2022 20:38
by philchillbill
Masterscot wrote:First of all, thank you for the scripts! They are excellent examples to work from.
I use them with Domoticz to play announcements in the house for certain events. Like door bell, garage door opens/closes, etc.
Very useful.
I thought I had a solution with the <playerid> connected ? command.
This gives back a 1 or 0 if LMS sees a player is connected or not.
Unfortunately this works only within the first couple of minutes a player is disconnected.
After that time I get the error back again. So maybe there is something at LMS not working as it should.
Have to try to make something work with the <serverstatus>.
Yes, if a player is disconnected and has been forgotten by LMS then it will be missing from the players_loop array returned by serverstatus. You can conclude from that that it must just be offline.

Re: Logitech Media Server

Posted: Tuesday 01 February 2022 20:59
by Masterscot
Do you know if there is a way to 'catch' this error in Perl?
Now it just exits the script with an error in the shell.
I mean, the errors which break the script like <serverstatus> or <connected> when the player is forgotten.

Re: Logitech Media Server

Posted: Tuesday 01 February 2022 21:09
by philchillbill
Masterscot wrote:Do you know if there is a way to 'catch' this error in Perl?
Now it just exits the script with an error in the shell.
I mean, the errors which break the script like <serverstatus> or <connected> when the player is forgotten.
You wrap the offending code in an eval(). See the Perl docs.

Re: Logitech Media Server

Posted: Wednesday 02 February 2022 12:21
by Masterscot
Got it working.
Before the rest of the code in the sub, I test which players are seen (and connected!) by LMS with <serverstatus>.
Those players go into a new array @players2, which is then used by the rest of the sub.
To prevent a further crash of the script it's also needed to check if a player, which is seen by LMS, is really connected.
That takes care of the moment a player is switched off, but still is seen by LMS (not yet forgotten).
@philchillbill, thanks for the help.

Re: Logitech Media Server

Posted: Wednesday 02 February 2022 12:49
by philchillbill
Masterscot wrote:Got it working.
Before the rest of the code in the sub, I test which players are seen (and connected!) by LMS with <serverstatus>.
Those players go into a new array @players2, which is then used by the rest of the sub.
To prevent a further crash of the script it's also needed to check if a player, which is seen by LMS, is really connected.
That takes care of the moment a player is switched off, but still is seen by LMS (not yet forgotten).
@philchillbill, thanks for the help.
Good stuff Image Feel free to update the wiki article with your improved code Image

Re: Logitech Media Server

Posted: Wednesday 02 February 2022 13:14
by Masterscot
Well, if somebody can use this, feel free to contact me.
This is more a 'hack' then probably good Perl programming.
And as such maybe not suitable as a public example, without a review.

Re: Logitech Media Server

Posted: Monday 07 February 2022 22:50
by Nautilus
Please post it as an example here, would be interested to see (I have some player in my setup which are not always available) :)

Re: Logitech Media Server

Posted: Wednesday 23 March 2022 13:18
by Nautilus
Getting this now: viewtopic.php?p=288285#p288285
Nautilus wrote: Wednesday 23 March 2022 13:14 I have enabled airplay capable devices to show up as LMS players. From Domoticz viewpoint this worked well in my other setup at summer cottage, but now when trying to do the same at home I just get this "Error: EventSystem: Could not find device in system: (idx 0)" in the logs and no players created.

Allow new devices is on, I've put both players on, I've disabled and enabled the plugin but nothing helps. Any ideas?
Would like to avoid deleting the hardware as it then screws up many other things that will need setting up again... :)

Re: Logitech Media Server

Posted: Tuesday 19 April 2022 20:23
by Masterscot
Here you go. Use it at your own risk.

#!/usr/bin/perl

# Plays a text-to-speech (TTS) alert on a Squeezebox, temporarily interrupting any music that's playing and then restoring
# An optional pre-chime doodle is played to first get our attention

# THIS VERSION ASSEMBLES/DISASSEMBLES TEMPORARY SYNCGROUPS FOR THE DURATION OF THE ALERT

# Author: philchillbill, 2017-2018

use JSON::RPC::Legacy::Client;
use JSON::XS;
no warnings 'uninitialized';
#use strict; use warnings;
# -----------------------------------------------------------------------------
# We call this perl script from the command line, passing 4 parameters to it:

# 1. 'players' is a string consisting of a "-" separated list of player names (no spaces!) in quotes.
# e.g. "kitchen" or "kitchen, livingroom, bedroom" but NOT "kitchen, living room, bedroom"
# 2. 'prechime' is the filename of the jingle to be played just prior to the spoken alert to grab our attention. e.g. 'Ring06.wav'.
# If no pre-chime is desired, use 'none'. The folder 'C:\Windows\media' has nice examples RingXX.wav.
# 3. 'ttsfile' is the filename of the spoken alert message. e.g. 'garageDoorOpen.mp3'
# 4. 'avol' is the volume the alert is to be played at and should be 0-100. In practice, values of 40-80 are realistic.

# Edit the following parameters to suit your setup:

# The mac addresses for all the squeezebox players that will use alerting
%uuids = (
bathroom => '00:04:20:xx:yy:zz',
kitchen => '00:04:20:xx:yy:zz',
livingroom => '00:04:20:xx:yy:zz'
);

# The full path to the directory where all the TTS mp3/wav files are stored. Please use the name 'alerts' somewhere in it.
%dir = (
alerts => 'file:///media/alerts'
);

# The URL for the LMS server instance, usually on port 9000 or 9002
%url = (
lms => 'http://aa.bb.cc.dd:9000/jsonrpc.js'
);

# -----------------------------------------------------------------------------

sub squeezeAlert {
($sbgroup, $prechime, $afile, $avol)=@_;
@players=@{$sbgroup};
$client=new JSON::RPC::Legacy::Client;

foreach $sb (@players) {
$playerid=$uuids{$sb};
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['serverstatus', 0, 64]] });
if($res->is_success) {
$jres = $res->jsontext;
$jdata=JSON::XS->new->allow_nonref->decode($jres);
$counter = 0;
while (defined($resultaat=$$jdata{result}{'players_loop'}[$counter]{'playerid'} )) {
$connected=$$jdata{result}{'players_loop'}[$counter]{'connected'};
if(($playerid eq $resultaat) & $connected == 1) {
push @players2, $sb;
# print "Player-name: $sb\n";
# print "Player online and connected: $resultaat\n";
}
$counter++;
}
} else { return }
}

#print "@players2\n";

# Get each player's present state so it can be restored later, but not if last-playing was an alert (path is checked)
foreach $sb (@players2) {
$playerid=$uuids{$sb};
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['playlist', 'path', '?'] ] });
if (!$res) { if (scalar(@players2)==1) { return } else { next } }; # player must be disconnected/offline so skip
if ($res->is_success) {
push(@alive, $sb); # add sb to the array of connected players
$jres=$res->jsontext; $jdata=JSON::XS->new->allow_nonref->decode($jres); $path=$$jdata{result}{_path} || 'empty';
$restore{$sb}=($path!~m/alerts|^empty$/) || 0;
if ($restore{$sb}) {
$res = ( $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['status', '-', '1', 'tags' ] ] }) ) || 0;
$jres = $res->jsontext; $jdata=JSON::XS->new->allow_nonref->decode($jres);
$repeat{$sb}=$$jdata{result}{'playlist repeat'};
$power{$sb}=$$jdata{result}{power};
$mode{$sb}=$$jdata{result}{mode}; $noplay{$sb}=($mode{$sb}!~m/play/) || 0;
$mvol{$sb}=$$jdata{result}{'mixer volume'};
$time{$sb}=$$jdata{result}{time};
if ($mode{$sb} eq 'play') { $res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['pause'] ] }); }
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['playlist', 'save', "nowplaying$sb", 'silent:1'] ] });
}}
# now we have the present state of the player, get ready to play the alert:
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['power', '1'] ] });
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['mixer', 'volume', $avol ] ] });
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['playlist', 'repeat', '0'] ] });
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['playlist', 'clear'] ] });
}
# replace the players-list by the list of verified connected players
@players2=@alive;
# build the sync group, if more than one player specified
$master=shift @players2;
foreach $sb (@players2) {
$res = $client->call($url{lms}, { method => 'slim.request', params => [ $uuids{$master}, ['sync', $uuids{$sb} ] ] });
}

# build prechime playlist and play the prechime first
$filetoplay="$dir{alerts}/$prechime";
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $uuids{$master}, ['playlist', 'add', "$filetoplay"] ] });
# now play the prechime-playlist
# $res = $client->call( $url{lms}, { method => 'slim.request', params => [ $uuids{$master}, ['play'] ] });

# build the alert playlist and play the alert second
$filetoplay="$dir{alerts}/$afile";
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $uuids{$master}, ['playlist', 'add', "$filetoplay"] ] });
# now play the alert
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $uuids{$master}, ['play'] ] });

# and wait for it to stop
do {
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $uuids{$master}, ['mode', '?'] ] });
$jres = $res->jsontext;
$jdata=JSON::XS->new->allow_nonref->decode($jres);
$mode=$$jdata{result}{_mode};
# sleep 5;
} until ($mode eq 'stop');

# ok, we are done playing so either restore the music playlist we interrupted, or power off if nothing was playing
foreach $sb ($master, @players2) {
$playerid=$uuids{$sb}; $res = $client->call($url{lms}, { method => 'slim.request', params => [ $playerid, ['sync', '-' ] ] });
if ($restore{$sb}) {
# Restore player to previous state
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['mixer', 'volume', $mvol{$sb} ] ] });
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['playlist', 'repeat', $repeat{$sb} ] ] });
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['playlist', 'resume', "nowplaying$sb", "noplay:$noplay{$sb}" ] ] }); ;
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['time', $time{$sb} ] ] });
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['power', $power{$sb}] ] });
}
else { # no restore, just clear and power-off
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['playlist', 'clear'] ] });
$res = $client->call( $url{lms}, { method => 'slim.request', params => [ $playerid, ['power', '0'] ] });
}
}
return;
}

($players, $prechime, $ttsfile, $avol)=@ARGV;
@players=split(/-\s?/, $players); # Use a - between players
&squeezeAlert( \@players, $prechime, $ttsfile, $avol );

Re: Logitech Media Server

Posted: Wednesday 20 April 2022 11:12
by Nautilus
Excellent, thanks a lot! :)

Can you give an example on how to call the script? I am especially wondering about this bit:

# 1. 'players' is a string consisting of a "-" separated list of player names (no spaces!) in quotes.
# e.g. "kitchen" or "kitchen, livingroom, bedroom" but NOT "kitchen, living room, bedroom"

The example shows a comma separated list, not hyphen - which should be used? Also, my players do have spaces in the names. Does it mean I should change the names or just strip away the space when calling them from this script?

Re: Logitech Media Server

Posted: Wednesday 20 April 2022 21:11
by Masterscot
I have never got the script working with perfect synchronization of all connected players. Sometimes they worked and most of the time one or two lost part of the sound. Probably due to a not so good WIFI connection.
So what I do now is call the script multiple times, for each player once. That works better in my situation.

In Domoticz you can in a Lua script for example do:


commandArray = {}

if (devicechanged['Doorbell_back'] == 'On') then
print("** Doorbell activated **")
os.execute('/home/folder/domoticz/scripts/sqalert.sh "kitchen" Ding-dong.mp3 Doorbell.mp3 40')
os.execute('/home/folder/domoticz/scripts/sqalert.sh "bathroom" Ding-dong.mp3 Doorbell.mp3 40')
os.execute('/home/folder/domoticz/scripts/sqalert.sh "livingroom" Ding-dong.mp3 Doorbell.mp3 40')
end

return commandArray


This calls the following shell script sqalert.sh for each player:

#! /bin/bash

# (
# flock -x -w 10 200 || exit 1
/usr/bin/perl /home/folder/domoticz/scripts/squeezealert.pl $1 $2 $3 $4 > /dev/null 2>&1 &
# ) 200>/var/lock/.sqalert.exclusivelock

The second stage shell script was used because I experimented with a crude form of file locking. You can try that by uncommenting the three lines.
Don't remove the # before ! /bin/bash though.
If you don't use the file locking you can abandon the shell script and call the perl script directly from Lua.

If you want to try using all players with one call, use:
/home/folder/domoticz/scripts/sqalert.sh "livingroom-bathroom-kitchen" Ding-dong.mp3 Doorbell.mp3 40

You need to edit all this for your player names, folder names and names of the sound files you use.
It's always a good idea to not use any spaces in names, if you are not certain spaces can be used.
So in this case do not use spaces in your player names.

Re: Logitech Media Server

Posted: Friday 22 April 2022 15:25
by Nautilus
Thanks - will try what would work best for me... 8-)

Re: Logitech Media Server

Posted: Wednesday 01 March 2023 16:00
by Nautilus
Took a while to get around to testing this. Did I misundersand the "sync" side of things as I though this would allow me to unsync a player temporarily, play the alert, and then re-sync? Now it seems that a synced player is taken out of the sync group and not returned there?