96_Snapcast.pm: update from githab (Forum #113317)

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@22532 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rudolfkoenig 2020-08-03 18:41:38 +00:00
parent df8d264da9
commit 8a423c1460
2 changed files with 285 additions and 225 deletions

View File

@ -38,9 +38,10 @@ my %Snapcast_sets = (
"update" => 0, "update" => 0,
"volume" => 2, "volume" => 2,
"stream" => 2, "stream" => 2,
"name" => 2, "name" => 2,
"mute" => 2, "mute" => 2,
"latency" => 2, "latency" => 2,
"group" => 2,
); );
my %Snapcast_client_sets = ( my %Snapcast_client_sets = (
@ -49,31 +50,31 @@ my %Snapcast_client_sets = (
"name" => 1, "name" => 1,
"mute" => 0, "mute" => 0,
"latency" => 1, "latency" => 1,
"group" => 1,
); );
my %Snapcast_clientmethods = ( my %Snapcast_clientmethods = (
"name" => "Client.SetName", "name" => "Client.SetName",
"volume" => "Client.SetVolume", "volume" => "Client.SetVolume",
"mute" => "Client.SetMute", "mute" => "Client.SetVolume",
"stream" => "Client.SetStream", "stream" => "Group.SetStream",
"latency" => "Client.SetLatency" "latency" => "Client.SetLatency"
); );
sub Snapcast_Initialize($) { sub Snapcast_Initialize($) {
my ($hash) = @_; my ($hash) = @_;
use DevIo; use DevIo;
$hash->{DefFn} = 'Snapcast_Define'; $hash->{DefFn} = 'Snapcast_Define';
$hash->{UndefFn} = 'Snapcast_Undef'; $hash->{UndefFn} = 'Snapcast_Undef';
$hash->{SetFn} = 'Snapcast_Set'; $hash->{SetFn} = 'Snapcast_Set';
$hash->{GetFn} = 'Snapcast_Get'; $hash->{GetFn} = 'Snapcast_Get';
$hash->{WriteFn} = 'Snapcast_Write'; $hash->{WriteFn} = 'Snapcast_Write';
$hash->{ReadyFn} = 'Snapcast_Ready'; $hash->{ReadyFn} = 'Snapcast_Ready';
$hash->{AttrFn} = 'Snapcast_Attr'; $hash->{AttrFn} = 'Snapcast_Attr';
$hash->{ReadFn} = 'Snapcast_Read'; $hash->{ReadFn} = 'Snapcast_Read';
$hash->{AttrList} = $hash->{AttrList} =
"streamnext:all,playing constraintDummy constraints volumeStepSize volumeStepSizeSmall volumeStepSizeThreshold" "streamnext:all,playing constraintDummy constraints volumeStepSize volumeStepSizeSmall volumeStepSizeThreshold " . $readingFnAttributes;
. $readingFnAttributes;
} }
sub Snapcast_Define($$) { sub Snapcast_Define($$) {
@ -121,12 +122,12 @@ sub Snapcast_Connect($){
} }
sub Snapcast_Attr($$){ sub Snapcast_Attr($$){
my ($cmd, $name, $attr, $value) = @_; my ($cmd, $name, $attr, $value) = @_;
my $hash = $defs{$name}; my $hash = $defs{$name};
if ($cmd eq "set"){ if ($cmd eq "set"){
if($attr eq "streamnext"){ if($attr eq "streamnext"){
return "streamnext needs to be either all or playing" unless $value=~/(all)|(playing)/; return "streamnext needs to be either all or playing" unless $value=~/(all)|(playing)/;
} }
if($attr eq "volumeStepSize"){ if($attr eq "volumeStepSize"){
return "volumeStepSize needs to be a number between 1 and 100" unless $value>0 && $value <=100; return "volumeStepSize needs to be a number between 1 and 100" unless $value>0 && $value <=100;
} }
@ -137,7 +138,7 @@ sub Snapcast_Attr($$){
return "volumeStepSizeThreshold needs to be a number between 0 and 100" unless $value>=0 && $value <=100; return "volumeStepSizeThreshold needs to be a number between 0 and 100" unless $value>=0 && $value <=100;
} }
} }
return undef; return undef;
} }
sub Snapcast_Undef($$) { sub Snapcast_Undef($$) {
@ -152,56 +153,56 @@ sub Snapcast_Undef($$) {
sub Snapcast_Get($@) { sub Snapcast_Get($@) {
return "get is not supported by this module"; return "get is not supported by this module";
} }
sub Snapcast_Set($@) { sub Snapcast_Set($@) {
my ($hash, @param) = @_; my ($hash, @param) = @_;
return '"set Snapcast" needs at least one argument' if (int(@param) < 2); return '"set Snapcast" needs at least one argument' if (int(@param) < 2);
my $name = shift @param; my $name = shift @param;
my $opt = shift @param; my $opt = shift @param;
my $value = join(" ", @param); my $value = join(" ", @param);
# my $clientmod;
my %sets = ($hash->{MODE} eq "client") ? %Snapcast_client_sets : %Snapcast_sets; my %sets = ($hash->{MODE} eq "client") ? %Snapcast_client_sets : %Snapcast_sets;
if(!defined($sets{$opt})) { if(!defined($sets{$opt})) {
my @cList = keys %sets; my @cList = keys %sets;
return "Unknown argument $opt, choose one of " . join(" ", @cList); return "Unknown argument $opt, choose one of " . join(" ", @cList);
} }
if(@param < $sets{$opt}){ if(@param < $sets{$opt}){
return "$opt requires at least ".$sets{$opt}." arguments"; return "$opt requires at least ".$sets{$opt}." arguments";
} }
if($opt eq "update"){ if($opt eq "update"){
Snapcast_getStatus($hash); Snapcast_getStatus($hash);
return undef; return undef;
} }
if(defined($Snapcast_clientmethods{$opt})){ if(defined($Snapcast_clientmethods{$opt})){
my $client; my $client;
if($hash->{MODE} eq "client"){ if($hash->{MODE} eq "client"){
my $clientmod=$hash; my $clientmod=$hash;
$client=$hash->{NAME}; $client=$hash->{NAME};
$hash=$hash->{SERVER}; $hash=$hash->{SERVER};
$hash=$defs{$hash}; $hash=$defs{$hash};
$client=$clientmod->{ID}; $client = $clientmod->{ID};
return "Cannot find Server hash" unless defined ($hash); return "Cannot find Server hash" unless defined ($hash);
}else{ }else{
$client = shift @param; $client = shift @param;
$client = Snapcast_getId($hash,$client) unless $client eq "all";
} }
$value = join(" ", @param); $value = join(" ", @param);
return "client not found, use unique name, IP, or MAC as client identifier" unless defined($client); return "client not found, use unique name, IP, or MAC as client identifier" unless defined($client);
if($client eq "all"){ if($client eq "all"){
for(my $i=1;$i<=ReadingsVal($name,"clients",0);$i++){ for(my $i=1;$i<=ReadingsVal($name,"clients",0);$i++){
my $client = $hash->{STATUS}->{clients}->{"$i"}->{host}->{mac}; my $client = $hash->{STATUS}->{clients}->{"$i"}->{host}->{mac};
$client=~s/\://g; $client=~s/\://g;
my $res = Snapcast_setClient($hash,$client,$opt,$value); my $res = Snapcast_setClient($hash,$client,$opt,$value);
readingsSingleUpdate($hash,"lastError",$res,1) if defined ($res); readingsSingleUpdate($hash,"lastError",$res,1) if defined ($res);
} }
return undef; return undef;
} }
my $res = Snapcast_setClient($hash,$client,$opt,$value); my $res = Snapcast_setClient($hash,$client,$opt,$value);
readingsSingleUpdate($hash,"lastError",$res,1) if defined ($res); readingsSingleUpdate($hash,"lastError",$res,1) if defined ($res);
return undef; return undef;
} }
return "$opt not implemented"; return "$opt not implemented";
} }
sub Snapcast_Read($){ sub Snapcast_Read($){
@ -220,6 +221,11 @@ sub Snapcast_Read($){
}else { }else {
$hash->{PARTIAL} = ""; $hash->{PARTIAL} = "";
} }
###############################
# Log3 $name,2, "Buffer: $buf";
###############################
my @lines = split( /\n/, $buf ); my @lines = split( /\n/, $buf );
foreach my $line (@lines) { foreach my $line (@lines) {
# Hier die Results parsen # Hier die Results parsen
@ -236,6 +242,7 @@ sub Snapcast_Read($){
my $update=$decoded_json; my $update=$decoded_json;
if(defined ($hash->{"IDLIST"}->{$update->{id}})){ if(defined ($hash->{"IDLIST"}->{$update->{id}})){
my $id=$update->{id}; my $id=$update->{id};
#Log3 $name,2, "id: $id ";
if($hash->{"IDLIST"}->{$id}->{method} eq 'Server.GetStatus'){ if($hash->{"IDLIST"}->{$id}->{method} eq 'Server.GetStatus'){
delete $hash->{"IDLIST"}->{$id}; delete $hash->{"IDLIST"}->{$id};
return Snapcast_parseStatus($hash,$update); return Snapcast_parseStatus($hash,$update);
@ -245,48 +252,88 @@ sub Snapcast_Read($){
return undef; return undef;
} }
while ( my ($key, $value) = each %Snapcast_clientmethods){ while ( my ($key, $value) = each %Snapcast_clientmethods){
if($value eq $hash->{"IDLIST"}->{$id}->{method}){ if(($value eq $hash->{"IDLIST"}->{$id}->{method}) && $key ne "mute"){ #exclude mute here because muting is now integrated in SetVolume
my $client = $hash->{"IDLIST"}->{$id}->{params}->{client}; my $client = $hash->{"IDLIST"}->{$id}->{params}->{id};
$client=~s/\://g; $client=~s/\://g;
$key=~s/mute/muted/g; Log3 $name,2, "client: $client ";
if($key eq "muted"){ Log3 $name,2, "key: $key ";
$update->{result} = $update->{result} ? "true" : "false"; Log3 $name,2, "value: $value ";
}
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash,"clients_".$client."_".$key,$update->{result} );
readingsEndUpdate($hash,1);
my $clientmodule = $hash->{$client};
my $clienthash=$defs{$clientmodule};
return undef unless defined ($clienthash);
readingsBeginUpdate($clienthash);
readingsBulkUpdateIfChanged($clienthash,$key,$update->{result} );
readingsEndUpdate($clienthash,1);
if($key eq "volume"){ if($key eq "volume"){
my $temp_percent = $update->{result}->{volume}->{percent};
#Log3 $name,2, "percent: $temp_percent ";
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash,"clients_".$client."_muted",$update->{result}->{volume}->{muted} );
readingsBulkUpdateIfChanged($hash,"clients_".$client."_volume",$update->{result}->{volume}->{percent} );
readingsEndUpdate($hash,1);
my $clientmodule = $hash->{$client};
my $clienthash=$defs{$clientmodule};
my $maxvol = Snapcast_getVolumeConstraint($clienthash); my $maxvol = Snapcast_getVolumeConstraint($clienthash);
if($update->{result} > $maxvol){ if (defined $clientmodule) {
Snapcast_setClient($hash,$clienthash->{ID},"volume",$maxvol); readingsBeginUpdate($clienthash);
readingsBulkUpdateIfChanged($clienthash,"muted",$update->{result}->{volume}->{muted} );
readingsBulkUpdateIfChanged($clienthash,"volume",$update->{result}->{volume}->{percent} );
readingsEndUpdate($clienthash,1);
} }
} }
elsif($key eq "stream"){
#Log3 $name,2, "key: $key ";
my $group = $hash->{"IDLIST"}->{$id}->{params}->{id};
#Log3 $name,2, "group: $group ";
for(my $i=1;$i<=ReadingsVal($name,"clients",1);$i++){
$client = $hash->{STATUS}->{clients}->{"$i"}->{id};
my $client_group = ReadingsVal($hash->{NAME},"clients_".$client."_group","");
#Log3 $name,2, "client_group: $client_group ";
my $clientmodule = $hash->{$client};
my $clienthash=$defs{$clientmodule};
if ($group eq $client_group) {
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash,"clients_".$client."_stream_id",$update->{result}->{stream_id} );
readingsEndUpdate($hash,1);
if (defined $clientmodule) {
readingsBeginUpdate($clienthash);
readingsBulkUpdateIfChanged($clienthash,"stream_id",$update->{result}->{stream_id} );
readingsEndUpdate($clienthash,1);
}
}
}
}
else{
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash,"clients_".$client."_".$key,$update->{result});
readingsEndUpdate($hash,1);
my $clientmodule = $hash->{$client};
my $clienthash=$defs{$clientmodule};
return undef unless defined ($clienthash);
readingsBeginUpdate($clienthash);
readingsBulkUpdateIfChanged($clienthash,$key,$update->{result} );
readingsEndUpdate($clienthash,1);
}
} }
} }
delete $hash->{"IDLIST"}->{$id}; delete $hash->{"IDLIST"}->{$id};
return undef; return undef;
} }
elsif($update->{method}=~/Client\.OnDelete/){ elsif($update->{method}=~/Client\.OnDelete/){
my $s=$update->{params}->{data}; my $s=$update->{params}->{data};
fhem "deletereading $name clients.*"; fhem "deletereading $name clients.*";
Snapcast_getStatus($hash); Snapcast_getStatus($hash);
return undef; return undef;
} }
elsif($update->{method}=~/Client\./){ elsif($update->{method}=~/Client\./){
my $c=$update->{params}->{data}; my $c=$update->{params}->{data};
Snapcast_updateClient($hash,$c,0); Snapcast_updateClient($hash,$c,0);
return undef; return undef;
} }
elsif($update->{method}=~/Stream\./){ elsif($update->{method}=~/Stream\./){
my $s=$update->{params}->{data}; my $s=$update->{params}->{data};
Snapcast_updateStream($hash,$s,0); Snapcast_updateStream($hash,$s,0);
return undef; return undef;
}
elsif($update->{method}=~/Group\./){
my $s=$update->{params}->{data};
Snapcast_updateStream($hash,$s,0);
return undef;
} }
Log3 $name,2,"unknown JSON, please ontact module maintainer: $buf"; Log3 $name,2,"unknown JSON, please ontact module maintainer: $buf";
readingsSingleUpdate($hash,"lastError","unknown JSON, please ontact module maintainer: $buf",1); readingsSingleUpdate($hash,"lastError","unknown JSON, please ontact module maintainer: $buf",1);
@ -302,8 +349,8 @@ sub Snapcast_Ready($){
return; return;
} }
if ( ReadingsVal( $name, "state", "disconnected" ) eq "disconnected" ) { if ( ReadingsVal( $name, "state", "disconnected" ) eq "disconnected" ) {
fhem "deletereading ".$name." streams.*"; fhem "deletereading ".$name." streams.*";
fhem "deletereading ".$name." clients.*"; fhem "deletereading ".$name." clients.*";
DevIo_OpenDev($hash, 1,"Snapcast_onConnect"); DevIo_OpenDev($hash, 1,"Snapcast_onConnect");
return; return;
} }
@ -322,35 +369,39 @@ sub Snapcast_onConnect($)
} }
sub Snapcast_updateClient($$$){ sub Snapcast_updateClient($$$){
my ($hash,$c,$cnumber) = @_; my ($hash,$c,$cnumber) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if($cnumber==0){ if($cnumber==0){
$cnumber++; $cnumber++;
while(defined($hash->{STATUS}->{clients}->{"$cnumber"}) && $c->{host}->{mac} ne $hash->{STATUS}->{clients}->{"$cnumber"}->{host}->{mac}){$cnumber++} while(defined($hash->{STATUS}->{clients}->{"$cnumber"}) && $c->{host}->{mac} ne $hash->{STATUS}->{clients}->{"$cnumber"}->{host}->{mac}){$cnumber++}
if (not defined ($hash->{STATUS}->{clients}->{"$cnumber"})) { if (not defined ($hash->{STATUS}->{clients}->{"$cnumber"})) {
Snapcast_getStatus($hash); Snapcast_getStatus($hash);
return undef; return undef;
} }
} }
$hash->{STATUS}->{clients}->{"$cnumber"}=$c; $hash->{STATUS}->{clients}->{"$cnumber"}=$c;
my $id=$c->{id}? $c->{id} : $c->{host}->{mac}; # protocol version 2 has no id, but just the MAC, newer versions will have an ID. my $id=$c->{id}? $c->{id} : $c->{host}->{mac}; # protocol version 2 has no id, but just the MAC, newer versions will have an ID.
$id=~s/\://g; my $orig_id = $id;
$id =~ s/://g;
$hash->{STATUS}->{clients}->{"$cnumber"}->{id}=$id; $hash->{STATUS}->{clients}->{"$cnumber"}->{id}=$id;
$hash->{STATUS}->{clients}->{"$cnumber"}->{origid}=$orig_id;
my $clientmodule = $hash->{$id}; my $clientmodule = $hash->{$id};
my $clienthash=$defs{$clientmodule}; my $clienthash=$defs{$clientmodule};
readingsBeginUpdate($hash); readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash,"clients_".$id."_online",$c->{connected} ? 'true' : 'false' ); readingsBulkUpdateIfChanged($hash,"clients_".$id."_online",$c->{connected} ? 'true' : 'false' );
readingsBulkUpdateIfChanged($hash,"clients_".$id."_name",$c->{config}->{name} ? $c->{config}->{name} : $c->{host}->{name} ); readingsBulkUpdateIfChanged($hash,"clients_".$id."_name",$c->{config}->{name} ? $c->{config}->{name} : $c->{host}->{name} );
readingsBulkUpdateIfChanged($hash,"clients_".$id."_latency",$c->{config}->{latency} ); readingsBulkUpdateIfChanged($hash,"clients_".$id."_latency",$c->{config}->{latency} );
readingsBulkUpdateIfChanged($hash,"clients_".$id."_stream",$c->{config}->{stream} ); readingsBulkUpdateIfChanged($hash,"clients_".$id."_stream_id",$c->{config}->{stream_id} );
readingsBulkUpdateIfChanged($hash,"clients_".$id."_volume",$c->{config}->{volume}->{percent} ); readingsBulkUpdateIfChanged($hash,"clients_".$id."_volume",$c->{config}->{volume}->{percent} );
readingsBulkUpdateIfChanged($hash,"clients_".$id."_muted",$c->{config}->{volume}->{muted} ? 'true' : 'false' ); readingsBulkUpdateIfChanged($hash,"clients_".$id."_muted",$c->{config}->{volume}->{muted} ? 'true' : 'false' );
readingsBulkUpdateIfChanged($hash,"clients_".$id."_ip",$c->{host}->{ip} ); readingsBulkUpdateIfChanged($hash,"clients_".$id."_ip",$c->{host}->{ip} );
readingsBulkUpdateIfChanged($hash,"clients_".$id."_mac",$c->{host}->{mac}); readingsBulkUpdateIfChanged($hash,"clients_".$id."_mac",$c->{host}->{mac});
readingsBulkUpdateIfChanged($hash,"clients_".$id."_id",$id); readingsBulkUpdateIfChanged($hash,"clients_".$id."_id",$id);
readingsBulkUpdateIfChanged($hash,"clients_".$id."_origid",$orig_id);
readingsBulkUpdateIfChanged($hash,"clients_".$id."_nr",$cnumber); readingsBulkUpdateIfChanged($hash,"clients_".$id."_nr",$cnumber);
readingsBulkUpdateIfChanged($hash,"clients_".$id."_group",$c->{config}->{group_id});
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
return undef unless defined ($clienthash); return undef unless defined ($clienthash);
@ -359,12 +410,14 @@ sub Snapcast_updateClient($$$){
readingsBulkUpdateIfChanged($clienthash,"online",$c->{connected} ? 'true' : 'false' ); readingsBulkUpdateIfChanged($clienthash,"online",$c->{connected} ? 'true' : 'false' );
readingsBulkUpdateIfChanged($clienthash,"name",$c->{config}->{name} ? $c->{config}->{name} : $c->{host}->{name} ); readingsBulkUpdateIfChanged($clienthash,"name",$c->{config}->{name} ? $c->{config}->{name} : $c->{host}->{name} );
readingsBulkUpdateIfChanged($clienthash,"latency",$c->{config}->{latency} ); readingsBulkUpdateIfChanged($clienthash,"latency",$c->{config}->{latency} );
readingsBulkUpdateIfChanged($clienthash,"stream",$c->{config}->{stream} ); readingsBulkUpdateIfChanged($clienthash,"stream_id",$c->{config}->{stream_id} );
readingsBulkUpdateIfChanged($clienthash,"volume",$c->{config}->{volume}->{percent} ); readingsBulkUpdateIfChanged($clienthash,"volume",$c->{config}->{volume}->{percent} );
readingsBulkUpdateIfChanged($clienthash,"muted",$c->{config}->{volume}->{muted} ? 'true' : 'false' ); readingsBulkUpdateIfChanged($clienthash,"muted",$c->{config}->{volume}->{muted} ? 'true' : 'false' );
readingsBulkUpdateIfChanged($clienthash,"ip",$c->{host}->{ip} ); readingsBulkUpdateIfChanged($clienthash,"ip",$c->{host}->{ip} );
readingsBulkUpdateIfChanged($clienthash,"mac",$c->{host}->{mac}); readingsBulkUpdateIfChanged($clienthash,"mac",$c->{host}->{mac});
readingsBulkUpdateIfChanged($clienthash,"id",$id); readingsBulkUpdateIfChanged($clienthash,"id",$id);
readingsBulkUpdateIfChanged($clienthash,"origid",$orig_id);
readingsBulkUpdateIfChanged($clienthash,"group",$c->{config}->{group_id});
readingsEndUpdate($clienthash,1); readingsEndUpdate($clienthash,1);
my $maxvol = Snapcast_getVolumeConstraint($clienthash); my $maxvol = Snapcast_getVolumeConstraint($clienthash);
if($c->{config}->{volume}->{percent} > $maxvol){ if($c->{config}->{volume}->{percent} > $maxvol){
@ -373,31 +426,18 @@ sub Snapcast_updateClient($$$){
return undef; return undef;
} }
sub Snapcast_deleteClient($$$){
my ($hash,$id) = @_;
my $name = $hash->{NAME};
my $paramset;
my $cnumber = ReadingsVal($name,"clients_".$id."_nr","");
return undef unless defined($cnumber);
my $method="Server.DeleteClient";
$paramset->{client}=ReadingsVal($hash,"clients_".$id."_mac","");
Snapcast_Do($hash,$method,$paramset);
readingsSingleUpdate($hash,"state","Client Deleted: $cnumber",1);
Snapcast_getStatus($hash);
}
sub Snapcast_updateStream($$$){ sub Snapcast_updateStream($$$){
my ($hash,$s,$snumber) = @_; my ($hash,$s,$snumber) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if($snumber==0){ if($snumber==0){
$snumber++; $snumber++;
while(defined($hash->{STATUS}->{streams}->{"$snumber"}) && $s->{id} ne $hash->{STATUS}->{streams}->{"$snumber"}->{id}){$snumber++} while(defined($hash->{STATUS}->{streams}->{"$snumber"}) && $s->{id} ne $hash->{STATUS}->{streams}->{"$snumber"}->{id}){$snumber++}
if (not defined ($hash->{STATUS}->{streams}->{"$snumber"})){ return undef;} if (not defined ($hash->{STATUS}->{streams}->{"$snumber"})){ return undef;}
} }
$hash->{STATUS}->{streams}->{"$snumber"}=$s; $hash->{STATUS}->{streams}->{"$snumber"}=$s;
readingsBeginUpdate($hash); readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash,"streams_".$snumber."_id",$s->{id} ); readingsBulkUpdateIfChanged($hash,"streams_".$snumber."_id",$s->{id} );
readingsBulkUpdateIfChanged($hash,"streams_".$snumber."_status",$s->{status} ); readingsBulkUpdateIfChanged($hash,"streams_".$snumber."_status",$s->{status} );
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
} }
@ -441,103 +481,135 @@ sub Snapcast_getStatus($){
sub Snapcast_parseStatus($$){ sub Snapcast_parseStatus($$){
my ($hash,$status) = @_; my ($hash,$status) = @_;
my $streams=$status->{result}->{streams}; my $streams=$status->{result}->{server}->{streams};
my $clients=$status->{result}->{clients}; my $groups=$status->{result}->{server}->{groups};
my $server=$status->{result}->{server}; my $server=$status->{result}->{server}->{server};
$hash->{STATUS}->{server}=$server; $hash->{STATUS}->{server}=$server;
if(defined ($clients)){ if(defined ($groups)){
my @clients=@{$clients}; my @groups=@{$groups};
my $cnumber=1; my $gnumber=1;
foreach my $c(@clients){ my $cnumber=1;
Snapcast_updateClient($hash,$c,$cnumber); foreach my $g(@groups){
$cnumber++; my $groupstream=$g->{stream_id};
} my $groupid = $g->{id};
readingsBeginUpdate($hash); my $clients=$g->{clients};
readingsBulkUpdateIfChanged($hash,"clients",$cnumber-1 ); if(defined ($clients)){
readingsEndUpdate($hash,1); my @clients=@{$clients};
foreach my $c(@clients){
$c->{config}->{stream_id} = $groupstream; # insert "stream" field for every client
$c->{config}->{group_id} = $groupid; # insert "group_id" field for every client
Snapcast_updateClient($hash,$c,$cnumber);
$cnumber++;
}
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash,"clients",$cnumber-1 );
readingsEndUpdate($hash,1);
}
}
} }
if(defined ($streams)){ if(defined ($streams)){
my @streams=@{$streams} unless not defined ($streams); my @streams=@{$streams} unless not defined ($streams);
my $snumber=1; my $snumber=1;
foreach my $s(@streams){ foreach my $s(@streams){
Snapcast_updateStream($hash,$s,$snumber); Snapcast_updateStream($hash,$s,$snumber);
$snumber++; $snumber++;
} }
readingsBeginUpdate($hash); readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash,"streams",$snumber-1 ); readingsBulkUpdateIfChanged($hash,"streams",$snumber-1 );
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
} }
InternalTimer(gettimeofday() + 300, "Snapcast_getStatus", $hash, 1); # every minute, get the full update, also to apply changed vol constraints. InternalTimer(gettimeofday() + 300, "Snapcast_getStatus", $hash, 1); # every 5 Minutes, get the full update, also to apply changed vol constraints.
} }
sub Snapcast_setClient($$$$){ sub Snapcast_setClient($$$$){
my ($hash,$id,$param,$value) = @_; my ($hash,$id,$param,$value) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $method; my $method;
my $paramset; my $paramset;
my $cnumber = ReadingsVal($name,"clients_".$id."_nr",""); my $cnumber = ReadingsVal($name,"clients_".$id."_nr","");
return undef unless defined($cnumber); return undef unless defined($cnumber);
$paramset->{client}=ReadingsVal($name,"clients_".$id."_mac",""); $paramset->{id} = Snapcast_getId($hash,$id);
return undef unless defined($Snapcast_clientmethods{$param}); return undef unless defined($Snapcast_clientmethods{$param});
$method=$Snapcast_clientmethods{$param}; $method=$Snapcast_clientmethods{$param};
if($param eq "volumeConstraint"){ if($param eq "volumeConstraint"){
my @values=split(/ /,$value); my @values=split(/ /,$value);
my $match; my $match;
return "not enough parameters for volumeConstraint" unless @values>=2; return "not enough parameters for volumeConstraint" unless @values>=2;
if(@values%2){ # there is a match argument given because number is uneven if(@values%2){ # there is a match argument given because number is uneven
$match=pop(@values); $match=pop(@values);
}else{$match="_global_"} } else {
for(my $i=0;$i<@values;$i+=2){ $match="_global_";
return "wrong timeformat 00:00 - 24:00 for time/volume pair" unless $values[$i]=~/^(([0-1]?[0-9]|2[0-3]):[0-5][0-9])|24:00$/; }
return "wrong volumeformat 0 - 100 for time/volume pair" unless $values[$i+1]=~/^(0?[0-9]?[0-9]|100)$/; for(my $i=0;$i<@values;$i+=2){
} return "wrong timeformat 00:00 - 24:00 for time/volume pair" unless $values[$i]=~/^(([0-1]?[0-9]|2[0-3]):[0-5][0-9])|24:00$/;
#readingsSingleUpdate($hash,"volumeConstraint_".$mac."_".$match,$value,1); return "wrong volumeformat 0 - 100 for time/volume pair" unless $values[$i+1]=~/^(0?[0-9]?[0-9]|100)$/;
return undef; }
} return undef;
if($param eq "stream"){
$param="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
my $totalstreams=ReadingsVal($name,"streams","");
my $currentstream = ReadingsVal($name,"clients_".$id."_stream","");
$currentstream = Snapcast_getStreamNumber($hash,$currentstream);
my $newstream = $currentstream+1;
$newstream=1 unless $newstream <= $totalstreams;
$value=ReadingsVal($name,"streams_".$newstream."_id","");
}
}
if($param eq "mute" && (not (defined($value)) || $value eq '')){
my $muteState = ReadingsVal($name,"clients_".$id."_muted","");
$value = $muteState eq "true" || $muteState ==1 ? "false" : "true";
} }
# check if volume was given as increment or decrement, then find out current volume and calculate new volume if($param eq "stream"){
if($param eq "volume" && $value=~/^([\+\-])(\d{1,2})$/){ $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
my $direction = $1; $param="stream_id";
my $amount = $2; 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","");
my $currentstream = ReadingsVal($name,"clients_".$id."_stream_id","");
$currentstream = Snapcast_getStreamNumber($hash,$currentstream);
my $newstream = $currentstream+1;
$newstream=1 unless $newstream <= $totalstreams;
$value=ReadingsVal($name,"streams_".$newstream."_id","");
}
}
if($param eq "volume"){
my $currentVol = ReadingsVal($name,"clients_".$id."_volume",""); my $currentVol = ReadingsVal($name,"clients_".$id."_volume","");
my $muteState = ReadingsVal($name,"clients_".$id."_muted","");
return undef unless defined($currentVol); return undef unless defined($currentVol);
if($direction eq "+"){$value = $currentVol + $amount;}else{$value = $currentVol - $amount;}
$value = 100 if ($value >= 100); # check if volume was given as increment or decrement, then find out current volume and calculate new volume
$value = 0 if ($value <0); if($value=~/^([\+\-])(\d{1,2})$/){
my $direction = $1;
my $amount = $2;
$value = eval($currentVol. $direction. $amount);
$value = 100 if ($value >= 100);
$value = 0 if ($value <0);
}
# if volume is given with up or down argument, then increase or decrease according to volumeStepSize
if($value=~/^(up|down)$/){
my $step = AttrVal($name,"volumeStepSizeThreshold",0) > $currentVol ? AttrVal($name,"volumeStepSizeSmall",3) : AttrVal($name,"volumeStepSize",7);
if ($value eq "up"){$value = $currentVol + $step;}else{$value = $currentVol - $step;}
$value = 100 if ($value >= 100);
$value = 0 if ($value <0);
$muteState = "false" if $value > 0 && ($muteState eq "true" || $muteState == 1);
}
my $volumeobject->{muted} = $muteState;
$volumeobject->{percent} = $value+0;
$value = $volumeobject;
} }
# if volume is given with up or down argument, then increase or decrease according to volumeStepSize
if($param eq "volume" && $value=~/^(up|down)$/){ if($param eq "mute" ){
my $currentVol = ReadingsVal($name,"clients_".$id."_volume",""); my $currentVol = ReadingsVal($name,"clients_".$id."_volume","");
my $muteState = ReadingsVal($name,"clients_".$id."_mute",""); my $volumeobject->{muted} = $value;
return undef unless defined($currentVol); $volumeobject->{percent} = $currentVol+0;
my $step = AttrVal($name,"volumeStepSizeThreshold",0) > $currentVol ? AttrVal($name,"volumeStepSizeSmall",3) : AttrVal($name,"volumeStepSize",7); $value = $volumeobject;
if ($value eq "up"){$value = $currentVol + $step;}else{$value = $currentVol - $step;}
$value = 100 if ($value >= 100); if(not (defined($value->{muted})) || $value->{muted} eq ''){
$value = 0 if ($value <0); my $muteState = ReadingsVal($name,"clients_".$id."_muted","");
Snapcast_setClient($hash,$id,"mute","false") if $value > 0 && ($muteState eq "true" || $muteState ==1) ; my $currentVol = ReadingsVal($name,"clients_".$id."_volume","");
$value = $muteState eq "true" || $muteState == 1 ? "false" : "true";
my $volumeobject->{muted} = $value;
$volumeobject->{percent} = $currentVol+0;
$value = $volumeobject;
}
$param = "volume"; # change param to "volume" to match new format
} }
if(looks_like_number($value)){
$paramset->{"$param"} = $value+0; if(looks_like_number($value)){
}else{ $paramset->{"$param"} = $value+0;
$paramset->{"$param"} = $value }else{
} $paramset->{"$param"} = $value
Snapcast_Do($hash,$method,$paramset); }
Snapcast_Do($hash,$method,$paramset);
return undef; return undef;
} }
@ -562,44 +634,32 @@ sub Snapcast_Encode($$$){
$hash->{"IDLIST"}->{$request->{id}} = $request; $hash->{"IDLIST"}->{$request->{id}} = $request;
$request->{id}=$request->{id}+0; $request->{id}=$request->{id}+0;
$json=encode_json($request)."\r\n"; $json=encode_json($request)."\r\n";
$json =~s/\"true\"/true/; # Snapcast needs bool values without "" but encode_json does not do this $json =~s/\"true\"/true/; # Snapcast needs bool values without "" but encode_json does not do this
$json =~s/\"false\"/false/; $json =~s/\"false\"/false/;
return $json; return $json;
} }
sub Snapcast_getStreamNumber($$){ sub Snapcast_getStreamNumber($$){
my ($hash,$id) = @_; my ($hash,$id) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
for(my $i=1;$i<=ReadingsVal($name,"streams",1);$i++){ for(my $i=1;$i<=ReadingsVal($name,"streams",1);$i++){
if ($id eq ReadingsVal($name,"streams_".$i."_id","")){ if ($id eq ReadingsVal($name,"streams_".$i."_id","")){
return $i; return $i;
} }
} }
return undef; return undef;
} }
sub Snapcast_getId($$){ sub Snapcast_getId($$){
my ($hash,$client) = @_; my ($hash,$client) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if($client=~/^([0-9a-f]{2}([:-]|$)){6}$/i){ # client is already a MAC if($client=~/^([0-9a-f]{12}(\#*\d*|$))$/i){ # client is ID
for(my $i=1;$i<=ReadingsVal($name,"clients",1);$i++){ for(my $i=1;$i<=ReadingsVal($name,"clients",1);$i++){
if ($client eq $hash->{STATUS}->{clients}->{"$i"}->{host}->{mac}) { if ($client eq $hash->{STATUS}->{clients}->{"$i"}->{id}) {
return $hash->{STATUS}->{clients}->{"$i"}->{id}; return $hash->{STATUS}->{clients}->{"$i"}->{origid};
} }
} }
} }
if($client =~ qr/^(?!(\.))(\.?(\d{1,3})(?(?{$^N > 255})(*FAIL))){4}$/){ # client is given as IP address
for(my $i=1;$i<=ReadingsVal($name,"clients",1);$i++){
if ($client eq $hash->{STATUS}->{clients}->{"$i"}->{host}->{ip}) {
return $hash->{STATUS}->{clients}->{"$i"}->{id};
}
}
}
for(my $i=1;$i<=ReadingsVal($name,"clients",1);$i++){
if ($client eq $hash->{STATUS}->{clients}->{"$i"}->{config}->{name}) {
return $hash->{STATUS}->{clients}->{"$i"}->{id};
}
}
return "unknown client"; return "unknown client";
} }
@ -621,7 +681,7 @@ sub Snapcast_getVolumeConstraint{
$list =~ s/^\s+//; # get rid of whitespaces $list =~ s/^\s+//; # get rid of whitespaces
$list =~ s/\s+$//; $list =~ s/\s+$//;
my @listelements=split(" ", $list); my @listelements=split(" ", $list);
my $mindiff=time_str2num($tomorrow."23:59:00"); # eine Tageslänge my $mindiff=time_str2num($tomorrow."23:59:00"); # eine Tageslänge
for(my $i=0;$i<@listelements/2;$i++){ for(my $i=0;$i<@listelements/2;$i++){
my $diff=abstime2rel($listelements[$i*2].":00"); # wie lange sind wir weg von der SChaltzeit? my $diff=abstime2rel($listelements[$i*2].":00"); # wie lange sind wir weg von der SChaltzeit?
if(time_str2num($tomorrow.$diff)<$mindiff){$mindiff=time_str2num($tomorrow.$diff);$value=$listelements[1+($i*2)];} # wir suchen die kleinste relative Zeit if(time_str2num($tomorrow.$diff)<$mindiff){$mindiff=time_str2num($tomorrow.$diff);$value=$listelements[1+($i*2)];} # wir suchen die kleinste relative Zeit
@ -629,7 +689,7 @@ sub Snapcast_getVolumeConstraint{
} }
} }
readingsSingleUpdate($hash,"maxvol",$value,1); readingsSingleUpdate($hash,"maxvol",$value,1);
return $value; # der aktuelle Auto-Wert wird zurückgegeben return $value; # der aktuelle Auto-Wert wird zurückgegeben
} }
sub Snapcast_isPmInstalled($$) sub Snapcast_isPmInstalled($$)
@ -736,4 +796,4 @@ sub Snapcast_isPmInstalled($$)
=end html =end html
=currentstream =currentstream

View File

@ -455,7 +455,7 @@ FHEM/95_remotecontrol.pm ulimaass Frontends
FHEM/95_YAAHM.pm pahenning Unterstützende Dienste FHEM/95_YAAHM.pm pahenning Unterstützende Dienste
FHEM/96_allowed.pm rudolfkoenig Automatisierung FHEM/96_allowed.pm rudolfkoenig Automatisierung
FHEM/96_SIP.pm Wzut,plin Sonstiges FHEM/96_SIP.pm Wzut,plin Sonstiges
FHEM/96_Snapcast.pm unimatrix Multimedia FHEM/96_Snapcast.pm rudolfkoenig/orphan Multimedia
FHEM/97_TrashCal.pm Tobias Unterstützende Dienste FHEM/97_TrashCal.pm Tobias Unterstützende Dienste
FHEM/98_alarmclock.pm FlorianZ Unterstützende Dienste FHEM/98_alarmclock.pm FlorianZ Unterstützende Dienste
FHEM/98_apptime.pm martinp876 Sonstiges FHEM/98_apptime.pm martinp876 Sonstiges