2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-03 16:56:54 +00:00

96_Snapcast: possible fix for mute problem

git-svn-id: https://svn.fhem.de/fhem/trunk@26597 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
Beta-User 2022-10-27 04:08:31 +00:00
parent 5c2cb94f4d
commit 2bdefd267b

View File

@ -31,7 +31,7 @@ use Time::HiRes qw( gettimeofday );
use DevIo; use DevIo;
use JSON (); use JSON ();
use GPUtils qw( GP_Import ); use GPUtils qw( GP_Import );
use List::Util qw( max min ); use List::Util 1.45 qw( max min uniq );
#-- Run before package compilation #-- Run before package compilation
@ -85,7 +85,8 @@ my %_clientmethods = (
volume => 'Client.SetVolume', volume => 'Client.SetVolume',
mute => 'Client.SetVolume', mute => 'Client.SetVolume',
stream => 'Group.SetStream', stream => 'Group.SetStream',
latency => 'Client.SetLatency' latency => 'Client.SetLatency',
group => 'Group.SetClients'
); );
=pod =pod
@ -262,38 +263,30 @@ sub Set {
} }
return; return;
} }
my @ids = grep {m{\A(?:clients_.+_group)\z}xms}
keys %{ $hash->{READINGS} }; my $grp = _getGroupMembers($hash, $client);
my @group; if ( @{$grp} ) {
for my $sid ( @ids ) { my $sparm;
my $gr = ReadingsVal($name, $sid, ''); my @paramset;
if ( $client eq $gr ) {
$sid =~ m{\Aclients_(.+)_group\z}xms;
push @group, $1;
};
#clients_84a93e695051_2_group a269028b-7078-210f-0e75-54acd507faaa
}
Log3( $hash, 5, "Snap: group members for arg. $client are @group within @ids");
if ( @group ) {
if ( $opt eq 'volume' && looks_like_number($value) && $value !~ m{[+-]}x ) { if ( $opt eq 'volume' && looks_like_number($value) && $value !~ m{[+-]}x ) {
#Log3($hash,3,"SNAP: Group absolute volume command, volume: $value"); #Log3($hash,3,"SNAP: Group absolute volume command, volume: $value");
my @paramset;
my $grvol; my $grvol;
for my $sclient ( @group ) { for my $sclient ( @{$grp} ) {
$grvol += ReadingsNum( $name, "clients_${sclient}_volume", 0); $grvol += ReadingsNum( $name, "clients_${sclient}_volume", 0);
} }
$grvol = int $grvol/@group; $grvol = int $grvol/@{$grp};
my $change = $value - $grvol; my $change = $value - $grvol;
for my $sclient ( @group ) { for my $sclient ( @{$grp} ) {
my $sparm->{id} = ReadingsVal( $name, "clients_${sclient}_origid", undef) // next;#_getId( $hash, $sclient) // next; $sparm->{id} = ReadingsVal( $name, "clients_${sclient}_origid", undef) // next;#_getId( $hash, $sclient) // next;
my $vol = ReadingsNum( $name, "clients_${sclient}_volume", 0) + $change; my $vol = ReadingsNum( $name, "clients_${sclient}_volume", 0) + $change;
$vol = max( 0, min( 100, $vol ) ); $vol = max( 0, min( 100, $vol ) );
my $muteState = ReadingsVal( $name, "clients_${sclient}_muted", 'false' ); my $muteState = ReadingsVal( $name, "clients_${sclient}_muted", 'false' );
$muteState = 'false' if $vol && ( $muteState eq 'true' || $muteState eq '1' ); $muteState = 'false' if $vol && ( $muteState eq 'true' || $muteState eq '1' );
$sparm->{volume}->{muted} = $muteState; $sparm->{volume}->{muted} = $muteState;
$sparm->{volume}->{percent} = $vol; $sparm->{volume}->{percent} = $vol;
my $payload = push @paramset, Snapcast_Encode( $hash, $_clientmethods{volume}, $sparm); push @paramset, Snapcast_Encode( $hash, $_clientmethods{volume}, $sparm, 1);
} }
return if !@paramset; return if !@paramset;
my $payload = q{[}; my $payload = q{[};
$payload .= join q{,},@paramset; $payload .= join q{,},@paramset;
@ -301,7 +294,30 @@ sub Set {
#Log3($hash,3,"SNAP: send batch $payload"); #Log3($hash,3,"SNAP: send batch $payload");
return DevIo_SimpleWrite( $hash, $payload, 2 ); return DevIo_SimpleWrite( $hash, $payload, 2 );
} }
for my $sclient ( @group ) { if ( $opt eq 'group' ) {
Log3( $hash, 3, "Snap: $opt command received for @{$grp}" );
my $opt2 = shift @param;
my $clnt = shift @param // return 'group commands require two additional arguments!';
$clnt = _getId( $hash, $clnt) // $clnt;
$sparm->{id} = $client; #needs group2id function as well
if ( $opt2 eq 'name' ) {
$sparm->{name} = $clnt;
return DevIo_SimpleWrite( $hash, Snapcast_Encode( $hash, 'Group.SetName', $sparm), 2 );
}
push @{$grp}, $clnt if $opt2 eq 'add';
my @grIds;
for ( @{$grp} ) {
push @grIds, _getId( $hash, $_) // $_;
}
@grIds = grep { $_ !~ m{\A$clnt\z}x } @grIds if $opt2 eq 'remove';
@grIds = uniq(@grIds);
$sparm->{clients} = \@grIds;
return DevIo_SimpleWrite( $hash, Snapcast_Encode( $hash, 'Group.SetClients', $sparm), 2 );
}
for my $sclient ( @{$grp} ) {
$sclient =~ s{:}{}gx; $sclient =~ s{:}{}gx;
$sclient =~ s{[#]}{_}gx; $sclient =~ s{[#]}{_}gx;
my $res = _setClient( $hash, $sclient, $opt, $value ); my $res = _setClient( $hash, $sclient, $opt, $value );
@ -316,6 +332,20 @@ sub Set {
return "$opt not implemented yet!"; return "$opt not implemented yet!";
} }
=pod
For <i>client</i> type devices, you may use a single group id as argument to add the client to the given group or the keyword <code>remove</code> to singularize that client.
Options for <i>server</i> type devices:
<ul>
<li><code>name &lt;group id&gt; &lt;new name&gt;</code></li>
<li><code> add &lt;client&gt; &lt;group id&gt;</code> add that client to the given group</li>
<li><code>remove &lt;client&gt; &lt;group id&gt;</code></li>
</ul>
Group.SetClients
{"id":3,"jsonrpc":"2.0","method":"Group.SetClients","params":{"clients":["00:21:6a:7d:74:fc#2","00:21:6a:7d:74:fc"],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
=cut
sub Read { sub Read {
my $hash = shift // return; my $hash = shift // return;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -428,6 +458,7 @@ sub Read {
readingsBeginUpdate($hash); readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged( $hash, "clients_${client}_$key", $update->{result} ); readingsBulkUpdateIfChanged( $hash, "clients_${client}_$key", $update->{result} );
readingsEndUpdate( $hash, 1 ); readingsEndUpdate( $hash, 1 );
if ( defined $hash->{$client} ) {
my $clienthash = $defs{$hash->{$client}} // return; my $clienthash = $defs{$hash->{$client}} // return;
readingsBeginUpdate($clienthash); readingsBeginUpdate($clienthash);
readingsBulkUpdateIfChanged( $clienthash, $key, $update->{result} ); readingsBulkUpdateIfChanged( $clienthash, $key, $update->{result} );
@ -435,6 +466,7 @@ sub Read {
} }
} }
} }
}
delete $hash->{IDLIST}->{$id}; delete $hash->{IDLIST}->{$id};
return; return;
} }
@ -741,7 +773,7 @@ sub _setClient {
if ( $param eq 'stream' ) { if ( $param eq 'stream' ) {
$paramset->{id} = ReadingsVal( $name, "clients_${id}_group", "" ); # for setting stream we now use group id instead of client id in snapcast 0.11 JSON format $paramset->{id} = ReadingsVal( $name, "clients_${id}_group", "" ); # for setting stream we now use group id instead of client id in snapcast 0.11 JSON format
$param = 'stream_id'; $param = 'stream_id';
if ( $value eq "next" ) { # just switch to the next stream, if last stream, jump to first one. This way streams can be cycled with a button press if ( $value eq 'next' ) { # just switch to the next stream, if last stream, jump to first one. This way streams can be cycled with a button press
my $totalstreams = ReadingsVal( $name, 'streams', 0 ); my $totalstreams = ReadingsVal( $name, 'streams', 0 );
my $currentstream = _getStreamNumber( $hash, ReadingsVal( $name, "clients_${id}_stream_id", '' ) ); my $currentstream = _getStreamNumber( $hash, ReadingsVal( $name, "clients_${id}_stream_id", '' ) );
my $newstream = $currentstream + 1; my $newstream = $currentstream + 1;
@ -774,7 +806,8 @@ sub _setClient {
$value = $currentVol - $step; $value = $currentVol - $step;
} }
$value = max( 0, min( 100, $value ) ); $value = max( 0, min( 100, $value ) );
$muteState = 'false' if ( $value > 0 && ( $muteState eq 'true' || $muteState == 1 )); $muteState = 'false' if ( $value > 0 && ( $muteState eq 'true' || $muteState ne '1' ));
$volumeobject->{muted} = $muteState;
} }
return if !looks_like_number($value); return if !looks_like_number($value);
$volumeobject->{percent} = $value + 0; $volumeobject->{percent} = $value + 0;
@ -783,14 +816,16 @@ sub _setClient {
if ( $param eq 'mute' ) { if ( $param eq 'mute' ) {
$volumeobject->{percent} = $currentVol + 0; $volumeobject->{percent} = $currentVol + 0;
if ( $value eq 'true' || $value eq 'false' ) {
$volumeobject->{muted} = $value;
} else {
$value = $volumeobject; $value = $volumeobject;
if ( !defined $value->{muted} || $value->{muted} eq '' ) { if ( !defined $value->{muted} || $value->{muted} eq '' ) {
$value = $muteState eq 'true' || $muteState == 1 ? 'false' : 'true'; $value = $muteState eq 'true' || $muteState == 1 ? 'false' : 'true';
$volumeobject->{muted} = $value; $volumeobject->{muted} = $value;
$volumeobject->{percent} = $currentVol + 0;
$value = $volumeobject;
} }
}
$value = $volumeobject;
$param = 'volume'; # change param to "volume" to match new format $param = 'volume'; # change param to "volume" to match new format
} }
@ -810,7 +845,6 @@ sub Snapcast_Do {
my $method = shift // return; my $method = shift // return;
my $param = shift // ''; my $param = shift // '';
my $payload = Snapcast_Encode( $hash, $method, $param ); my $payload = Snapcast_Encode( $hash, $method, $param );
$payload .= "\r\n";
#Log3($hash,3,"SNAP: Do $payload"); #Log3($hash,3,"SNAP: Do $payload");
return DevIo_SimpleWrite( $hash, $payload, 2 ); return DevIo_SimpleWrite( $hash, $payload, 2 );
@ -820,6 +854,7 @@ sub Snapcast_Encode {
my $hash = shift // return; my $hash = shift // return;
my $method = shift // return; my $method = shift // return;
my $param = shift // ''; my $param = shift // '';
my $nonl = shift;
if ( defined( $hash->{helper}{REQID} ) ) { $hash->{helper}{REQID}++; } if ( defined( $hash->{helper}{REQID} ) ) { $hash->{helper}{REQID}++; }
else { $hash->{helper}{REQID} = 1; } else { $hash->{helper}{REQID} = 1; }
@ -834,6 +869,7 @@ sub Snapcast_Encode {
$request->{id} = $request->{id} + 0; $request->{id} = $request->{id} + 0;
$json = JSON->new->encode($request); $json = JSON->new->encode($request);
$json =~ s{("(true|false|null)")}{$2}gxms; $json =~ s{("(true|false|null)")}{$2}gxms;
$json .= "\r\n" if !$nonl;
return $json; return $json;
} }
@ -847,6 +883,25 @@ sub _getStreamNumber {
return; return;
} }
sub _getGroupMembers {
my $hash = shift // return;
my $grid = shift // return;
my $name = $hash->{NAME} // return;
my @ids = grep {m{\A(?:clients_.+_group)\z}xms}
keys %{ $hash->{READINGS} };
my @group;
for my $sid ( @ids ) {
my $gr = ReadingsVal($name, $sid, '');
if ( $grid eq $gr ) {
$sid =~ m{\Aclients_(.+)_group\z}xms;
push @group, $1;
};
}
Log3( $hash, 5, "Snap: group members for arg. $grid are @group within @ids");
return \@group;
}
sub _getId { sub _getId {
my $hash = shift // return; my $hash = shift // return;
my $client = shift // return; my $client = shift // return;
@ -854,7 +909,7 @@ sub _getId {
my $name = $hash->{NAME} // return; my $name = $hash->{NAME} // return;
# client is ID # client is ID
if ( $client =~ m/^([0-9a-f]{12}([#_]*\d*|$))$/i ) { if ( $client =~ m{\A(?:[[:xdigit:]]{8}-(?:[[:xdigit:]]{4}-){3}[[:xdigit:]]{12}(?:[#_]*\d*)|[[:xdigit:]]{12}(?:[#_]*\d*))\z}i ) {
for my $i ( 1 .. ReadingsVal( $name, 'streams', 1 ) ) { for my $i ( 1 .. ReadingsVal( $name, 'streams', 1 ) ) {
return $hash->{STATUS}->{clients}->{$i}->{origid} return $hash->{STATUS}->{clients}->{$i}->{origid}
if $client eq $hash->{STATUS}->{clients}->{$i}->{id}; if $client eq $hash->{STATUS}->{clients}->{$i}->{id};
@ -954,9 +1009,14 @@ __END__
<a id="Snapcast-set-update"></a><li><i>update</i><br> <a id="Snapcast-set-update"></a><li><i>update</i><br>
Perform a full update of the Snapcast Status including streams and servers. Only needed if something is not working. Server module only</li> Perform a full update of the Snapcast Status including streams and servers. Only needed if something is not working. Server module only</li>
<a id="Snapcast-set-volume"></a><li><i>volume</i><br> <a id="Snapcast-set-volume"></a><li><i>volume</i><br>
Set the volume of a client. For this and all the following 4 options, give client as second parameter (only for the server module), either as name, IP , or MAC and the desired value as third parameter. Set the volume of a client. For this and all the following 4 options, give <i>target</i> as second parameter (only for the server module) and the desired value as third parameter.
Client can be given as "all", in that case all clients are changed at once (only for server module)<br> <i>target</i> can be given as
Volume can be given in 3 ways: Range between 0 and 100 to set volume directly. Increment or Decrement given between -100 and +100. Keywords <em>up</em> and <em>down</em> to increase or decrease with a predifined step size. <ul>
<li><i>client</i> either as name, IP or MAC</li>
<li><i>all</i> to change volume of all clients at once (only for server module)</li>
<li><i>group id</i> to change volume of all clients belonging to that group at once</li>
</ul>
<i>volume</i> can be given in 3 ways: Range between 0 and 100 to set volume directly. Increment or Decrement given between -100 and +100. Keywords <em>up</em> and <em>down</em> to increase or decrease with a predifined step size.
The step size can be defined in the attribute <em>volumeStepSize</em><br> The step size can be defined in the attribute <em>volumeStepSize</em><br>
The step size can be defined smaller for the lower volume range, so that finetuning is possible in this area. The step size can be defined smaller for the lower volume range, so that finetuning is possible in this area.
See the description of the attributes <em>volumeStepSizeSmall</em> and <em>volumeStepThreshold</em> See the description of the attributes <em>volumeStepSizeSmall</em> and <em>volumeStepThreshold</em>
@ -970,6 +1030,15 @@ __END__
<a id="Snapcast-set-stream"></a><li><i>stream</i><br> <a id="Snapcast-set-stream"></a><li><i>stream</i><br>
Change the stream that the client is listening to. Snapcast uses one or more streams which can be unterstood as virtual audio channels. Each client/room can subscribe to one of them. Change the stream that the client is listening to. Snapcast uses one or more streams which can be unterstood as virtual audio channels. Each client/room can subscribe to one of them.
By using next as value, you can cycle through the avaialble streams</li> By using next as value, you can cycle through the avaialble streams</li>
<a id="Snapcast-set-group"></a><li><i>group</i><br>
For <i>client</i> type devices, you may use a single group id as argument to add the client to the given group or the keyword <code>remove</code> to singularize that client.
Options for <i>server</i> type devices:
<ul>
<li><code>&lt;group id&gt; name &lt;new name&gt;</code></li>
<li><code>&lt;group id&gt; add &lt;client&gt;</code> add that client to the given group</li>
<li><code>&lt;group id&gt; remove &lt;client&gt; </code></li>
</ul>
</li>
</ul> </ul>
</ul> </ul>
<br><br> <br><br>