FHEM meets UPnP?

Begonnen von crazystone, 14 August 2013, 21:28:36

Vorheriges Thema - Nächstes Thema

Mauwges

Hi Betateilchen,

ich bin mal so unverschämt un falle mit der Tür ins Haus.
Bin gerade über diesen Thread gestolpert und über dein Beispiel deines UPNP-Moduls
Leider gabs keinen Link wo man das Ding runterladen könnte.
Ich weiß, dass es nicht fertig ist - jedoch könne es mir als Anker dienen um weiter zu entwickeln.

Ich will eigentlich nur mein Streamium per FHEM ein und ausschalten - von daher brauche ich keinen großen Funktionsumfang. Leider kenne ich mich mit UPNP nicht wirklich gut aus, da ich vor 4 Jahren als das Thema mal heiß war nur Enttäuschen erlebte und mich deshalb nicht weiter damit beschäftigen wollte.

Könntest du mir das Modul vlt. einfach schicken?

Viele Grüße
Markus

hokascha

Jepp, da wäre ich auch interessiert dran, gleiche Zielsetzung: Player remote starten und womöglich gleich füttern.

Grüße,

Kevin

betateilchen

Zitat von: Mauwges am 09 Januar 2014, 21:59:32Könntest du mir das Modul vlt. einfach schicken?

Hab ich leider nicht mehr.
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

hokascha

OK, ich hab mal angefangen, mich damit zu beschäftigen. Auf Basis von https://github.com/amgorb/simple-DLNA-remote-controller hab ich den DLNA-Teil soweit am laufen und kann meinem Player eine URL zum Abspielen übergeben (und den Player damit implizit einschalten). Habe dafür den ganzen Endlos-Loop etc. rausgeworfen, brauchen wir dafür ja nicht.
Jetzt müsste ich das ganze noch in ein Modul-Gerüst für FHEM verpacken, sodass am Ende ein "set DLNAClient http://mein.server/meine_musik.mp3" ausreicht, um den Player zu starten und Musik abzuspielen. Gibt da schon irgendwo eine Art Dummy-Modul, dass die FHEM-Konventionen implementiert?

Grüße,

Kevin

Kuzl

#34
Hi ich weiß zwar nicht über was das ganze jetzt läuft allerings ist das einfachste einfach ein fertiges Modul, z.b. STV zu verwenden und alles was man nicht braucht rauszuschmeissen. Die wichtigen sachen im Zusammenhang mit FHEM ist im Forum unter Devolopment verlinkt, ich suchs dir gleich raus, wenn ich Zuhause bin, bin sehr interessiert daran :)

EDIT: So bittesehr das ist ganz gut für den Anfang :) http://www.fhemwiki.de/wiki/DevelopmentModuleIntro

hokascha

Danke Kuzl, ich hab mich in der Zwischenzeit am dummy-Modul orientiert. Hier ist der aktuelle Stand, das ist noch weit weg von einsatzbar, schon allein weil es FHEM blockiert, während es nach DLNA-Renderern sucht, aber es funktioniert. Wie gesagt, dreckig von https://github.com/amgorb/simple-DLNA-remote-controller übernommen.
Womöglich findet sich ja jemand mit mehr FHEM-Modul-Erfahrung und geht da nochmal drüber. Ich bin mir z.B. nicht wirklich klar darüber, wann welche readingsSingleUpdate-Aufrufe gemacht werden müssen, etc.

##############################################
# $Id: 98_DLNAClient.pm 3738 2014-01-27 14:13:59Z hokascha $
package main;

use strict;
use warnings;
use Net::UPnP::ControlPoint;
use Net::UPnP::AV::MediaRenderer;
use Net::UPnP::ActionResponse;
use Net::UPnP::AV::MediaServer;

sub
DLNAClient_Initialize($)
{
  my ($hash) = @_;

  $hash->{SetFn}     = "dlna_Set";
  $hash->{DefFn}     = "dlna_Define";
  $hash->{AttrList}  = "server file";
}

###################################
sub
dlna_Set($@)
{
  my ($hash, @a) = @_;
  my $TVname = shift @a;
  my $stopbeforeplay=0;
  return "no set value specified" if(int(@a) < 1);
 
  my $v = join(" ", @a);
  Log3 $hash, 3, "DLNAClient set $TVname $v";
  $TVname = $hash->{CLIENTNAME};
 
  my $obj = Net::UPnP::ControlPoint->new();
    my @dev_list = ();
    my $retry_cnt = 0;
    while (@dev_list <= 0 ) {
Log3 $hash, 3,  "Searching for renderers.. @dev_list";
        @dev_list = $obj->search(st =>'urn:schemas-upnp-org:device:MediaRenderer:1', mx => 5);
        $retry_cnt++;
if ($retry_cnt >= 5) {
Log3 $hash, 3, "[!] No renderers found. Releasing semaphore, exiting.";

        return undef;
}
    }
     Log3 $hash, 3,  "Found $#dev_list renderers\n";

    my $devNum= 0;
    my $dev;
    foreach $dev (@dev_list) {
my $device_type = $dev->getdevicetype();
        if  ($device_type ne 'urn:schemas-upnp-org:device:MediaRenderer:1') {
            next;
        }

$devNum++;
        my $friendlyname = $dev->getfriendlyname();
Log3 $hash, 3, "found [$devNum] : device name: [" . $friendlyname . "] " ;
if ($friendlyname !~ /$TVname/) {  Log3 $hash, 3,  "skipping this device.";next;}

        my $renderer = Net::UPnP::AV::MediaRenderer->new();
        $renderer->setdevice($dev);
my $condir_service = $dev->getservicebyname('urn:schemas-upnp-org:service:AVTransport:1');
        my %action_in_arg = (
                'ObjectID' => 0,
                'InstanceID' => '0'
            );
        my $action_res = $condir_service->postcontrol('GetTransportInfo', \%action_in_arg);
        my $actrion_out_arg = $action_res->getargumentlist();
my $x = $actrion_out_arg->{'CurrentTransportState'};
Log3 $hash, 3, "Device current state is <<$x>>. ";
        if ( ($x !~ /PLAY/) || ($stopbeforeplay == 1) ) {
          if ($stopbeforeplay == 1) {
Log3 $hash, 3, "First run, force stop-start ";
    } else {  Log3 $hash, 3, "Device is in bad state, starting up! "; }
  } else {
    #Log3 $hash, 3,  "This is ok, skipping.";
    #next;
    }
       
my $file = $v;
my $meta = <<EOF;
&lt;DIDL-Lite xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot; xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot;&gt;&lt;item id=&quot;2\$8\$1B&quot; parentID=&quot;2\$15&quot; restricted=&quot;true&quot;&gt;  &lt;dc:title&gt;final_movie&lt;/dc:title&gt;  &lt;upnp:class&gt;object.item.videoItem&lt;/upnp:class&gt;  &lt;res protocolInfo=&quot;http-get:*:video/x-msvideo:*&quot; size=&quot;138332664&quot; duration=&quot;2:35:27.079&quot; resolution=&quot;1366x768&quot; bitrate=&quot;6002933&quot; sampleFrequency=&quot;44100&quot; nrAudioChannels=&quot;1&quot;&gt;$file&lt;/res&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;
EOF
my $id = 0;
        #$renderer->setAVTransportURI(InstanceID => $id, CurrentURI => $file, CurrentURIMetaData => $meta);

$renderer->setAVTransportURI(CurrentURI => $file);
if ($stopbeforeplay == 1) {
   Log3 $hash, 3, "device [$devNum] pid $$ command: [stop] \n";
   $renderer->stop();
   $stopbeforeplay = 0;
        }
  #Log3 $hash, 3, "device [$devNum] pid $$ command: [play] $fileid $file";sleep 1;
        $renderer->play();
        $devNum++;
readingsSingleUpdate($hash,"state",$v,1);
return undef;
 
    } #for each devices

  readingsSingleUpdate($hash,"state",$v,1);
  return undef;
}

sub
dlna_Define($$)
{
  my ($hash, $def) = @_;
  my @a = split("[ \t][ \t]*", $def);

  return "Wrong syntax: use define <name> DLNAClient <Name>" if(int(@a) != 3);
  my $name       = shift @a;
  my $type       = shift @a;
  my $clientName        = shift @a;
   $hash->{CLIENTNAME}     = $clientName;
  return undef;
}

1;

=pod
=begin html

<a name="DLNAClient"></a>
<h3>DLNAClient</h3>
<ul>

  Define a DLNA client. A DLNA client can take an URL to play via <a href="#set">set</a>.
 
  <br><br>

  <a name="DLNAClientdefine"></a>
  <b>Define</b>
  <ul>
    <code>define &lt;name&gt; DLNAClient &lt;regex&gt;</code>
    <br><br>

    Example:
    <ul>
      <code>define MyPlayer DLNAClient NP2500</code><br>
      Here, NP2500 is part of the name of the player as it announces itself to the network.<br/>
      <code>set MyPlayer http://link-to-my/file.mp3</code><br>
    </ul>
  </ul>
  <br>

  <a name="DLNAClientset"></a>
  <b>Set</b>
  <ul>
    <code>set &lt;name&gt; &lt;value&gt</code><br>
    Set any URL to play.
  </ul>
  <br>

  <a name="DLNAClientget"></a>
  <b>Get</b> <ul>N/A</ul><br>

  <a name="DLNAClientattr"></a>
  <b>Attributes</b>
  <ul>
    <li>server<br>
        NOT YET IMPLEMENTED - Name of DLNA Server library to search for given filename
        </li>
   
  </ul>
  <br>

</ul>

=end html
=cut


Ein Beispiel:

define MyPlayer DLNAClient NP2500
set MyPlayer http://link-to-my/file.mp3


Grüße,

Kevin

Kuzl

#36
Das ging ja fix :D
Könnte man die Suche nach einem DLNA-Renderer nicht einfach ins Define packen und dann abspeichern und beim set nur einfach dort hinschicken? Bin in der Hintergrundtechnik von DLNA leider nicht so informiert, kann auch sein, dass erst vor jedem Abspielen gesucht werden muss. Aber wenn nicht, lässt sich die FHEM-Blockade auf das define reduzieren, und das wäre ja hinnehmbar :)

Weißt du zufällig ob das auch auf einer Fritzbox läuft? ich bezweifle ehrlich gesagt, dass Net::UPnP vorhanden ist :D wäre aber sehr cool :)
Und noch eine Frage: heißt dass, dass die Datei gar nicht auf einem DLNA-Server sein muss, sondern nur über FTP-erreichbar sein muss? wenn ja, wo wird das gepuffert?

Viele Grüße
Kuzl

EDIT: Grad getestet, wird leider nicht gefunden :( gibts eine möglichkeit, das nachträglich einzubinden? mit Crosscompiler oder so

hokascha

ZitatKönnte man die Suche nach einem DLNA-Renderer nicht einfach ins Define packen und dann abspeichern und beim set nur einfach dort hinschicken? Bin in der Hintergrundtechnik von DLNA leider nicht so informiert, kann auch sein, dass erst vor jedem Abspielen gesucht werden muss. Aber wenn nicht, lässt sich die FHEM-Blockade auf das define reduzieren, und das wäre ja hinnehmbar :)

Ja, das ist denkbar. Solange sich die IP des Renderers nicht ändert, müsste das funktionieren.

ZitatWeißt du zufällig ob das auch auf einer Fritzbox läuft? ich bezweifle ehrlich gesagt, dass Net::UPnP vorhanden ist :D wäre aber sehr cool :)

Nein, kein Plan, ich betreibe FHEM nicht auf der Fritzbox.

ZitatUnd noch eine Frage: heißt dass, dass die Datei gar nicht auf einem DLNA-Server sein muss, sondern nur über FTP-erreichbar sein muss? wenn ja, wo wird das gepuffert?

Die Datei oder der Stream muss per HTTP oder was auch immer Dein Renderer (also Dein Fernseher oder WLAN-Radio, etc.) kann, erreichbar sein. Bei mir ist das einfach der PC, kann freilich auch ein NAS oder sonstwas sein. Gepuffert wird da gar nichts, der Renderer  bekommt nur die URL zum Medium mitgeteilt und holt es sich dann selbständig von dort ab. FHEM spielt also nur Controller.

Grüße,

Kevin

Kuzl

ZitatJa, das ist denkbar. Solange sich die IP des Renderers nicht ändert, müsste das funktionieren.
Ok dann könntest du das so machen, denn das wird bei fast allen FHEM-Modulen vorrausgesetzt und sollte auch nicht so tragisch sein :D

Ah okay das ist natürlich gut, dann ist man unabhängig von DLNA-Servern und kann beliebig abspielen lassen was man will :)

Leider ist mein FHEM zur zeit auf einer Fritzbox, sollte eigendlich auch so bleiben. Allerdings ist dann das wieder ein Grund, das ganze mal auf ein "vollwertiges" System zu setzen :D mal sehen irgendwie muss das schon gehen :)

Danke für die Erklärung, mir war bis heute nie ganz klar, ob jetzt der Renderer direkt auf die Datei zugreift, oder diese vom Controller zugespielt bekommt :D

Bin gespannt was da noch draus wird :)

Viele Grüße
Kuzl

hokascha

#39
So, jetzt das Suchen nach den Renderern in die Define-Methode verschoben und neben on, off noch play und stop als mögliche set-Befehle dazugefügt.

##############################################
# 2014-01-28 hokascha $
#
#  DLNA Module to play given URLs on a DLNA Renderer
#
##############################################
package main;

use strict;
use warnings;
use Net::UPnP::ControlPoint;
use Net::UPnP::AV::MediaRenderer;
use Net::UPnP::ActionResponse;
use Net::UPnP::AV::MediaServer;
use Data::Dumper;

sub
DLNAClient_Initialize($)
{
  my ($hash) = @_;

  $hash->{SetFn}     = "dlna_Set";
  $hash->{DefFn}     = "dlna_Define";
  $hash->{AttrList}  = "server file";
}

###################################
sub
dlna_Set($@)
{
my ($hash, @a) = @_;
my $TVname = shift @a;
my $stopbeforeplay=0;
return "no set value specified" if(int(@a) < 1);
return "Unknown argument, choose one of on off play stop <url>" if($a[0] eq "?");
my $v = join(" ", @a);
Log3 $hash, 3, "DLNAClient set $TVname $v";
$TVname = $hash->{CLIENTNAME};
my $dev = $hash->{DEV};
 
my $renderer = Net::UPnP::AV::MediaRenderer->new();
$renderer->setdevice($dev);
my $condir_service = $dev->getservicebyname('urn:schemas-upnp-org:service:AVTransport:1');
my %action_in_arg = (
'ObjectID' => 0,
'InstanceID' => '0'
    );
my $action_res = $condir_service->postcontrol('GetTransportInfo', \%action_in_arg);
my $actrion_out_arg = $action_res->getargumentlist();
#Log3 $hash, 3, "DLNAClient: ".Dumper($actrion_out_arg);
my $x = $actrion_out_arg->{'CurrentTransportState'};
Log3 $hash, 3, "DLNAClient: Device current state is <<$x>>. ";
if ( ($x !~ /PLAY/) || ($stopbeforeplay == 1) ) {
if ($stopbeforeplay == 1) {
Log3 $hash, 3, "DLNAClient: First run, force stop-start ";
$renderer->stop();
} else { 
Log3 $hash, 3, "DLNAClient: Device is in bad state, starting up! ";
}
} else {
#Log3 $hash, 3,  "This is ok, skipping.";
#next;
}

if($a[0] eq "off" || $a[0] eq "stop" ){
$renderer->stop();
readingsSingleUpdate($hash,"state",$v,1);
return undef;
}
if($a[0] eq "on" || $a[0] eq "play"){
$renderer->play();
readingsSingleUpdate($hash,"state",$v,1);
return undef;
}

my $file = $v;
# my $meta = <<EOF;
#&lt;DIDL-Lite xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot; xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot;&gt;&lt;item id=&quot;2\$8\$1B&quot; parentID=&quot;2\$15&quot; restricted=&quot;true&quot;&gt;  &lt;dc:title&gt;final_movie&lt;/dc:title&gt;  &lt;upnp:class&gt;object.item.videoItem&lt;/upnp:class&gt;  &lt;res protocolInfo=&quot;http-get:*:video/x-msvideo:*&quot; size=&quot;138332664&quot; duration=&quot;2:35:27.079&quot; resolution=&quot;1366x768&quot; bitrate=&quot;6002933&quot; sampleFrequency=&quot;44100&quot; nrAudioChannels=&quot;1&quot;&gt;$file&lt;/res&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;
#EOF
my $id = 0;
#$renderer->setAVTransportURI(InstanceID => $id, CurrentURI => $file, CurrentURIMetaData => $meta);

$renderer->setAVTransportURI(CurrentURI => $file);
if ($stopbeforeplay == 1) {
   $renderer->stop();
   $stopbeforeplay = 0;
}

$renderer->play();

readingsSingleUpdate($hash,"state",$v,1);
return undef;
}

sub
dlna_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);

return "Wrong syntax: use define <name> DLNAClient <Name>" if(int(@a) != 3);
my $name       = shift @a;
my $type       = shift @a;
my $clientName        = shift @a;
$hash->{CLIENTNAME}     = $clientName;
my $obj = Net::UPnP::ControlPoint->new();
my @dev_list = ();
my $retry_cnt = 0;
while (@dev_list <= 0 ) {
Log3 $hash, 3,  "DLNAClient: Searching for renderers... @dev_list";
@dev_list = $obj->search(st =>'urn:schemas-upnp-org:device:MediaRenderer:1', mx => 5);
$retry_cnt++;
if ($retry_cnt >= 5) {
Log3 $hash, 3, "DLNAClient: [!] No renderers found. Exiting.";
return undef;
}
}

my $devNum= 0;
my $dev;
foreach $dev (@dev_list) {
my $device_type = $dev->getdevicetype();
if  ($device_type ne 'urn:schemas-upnp-org:device:MediaRenderer:1') {
    next;
}
$devNum++;
my $friendlyname = $dev->getfriendlyname();
Log3 $hash, 3, "DLNAClient: found [$devNum] : device name: [" . $friendlyname . "] " ;
if ($friendlyname !~ /$clientName/) {  Log3 $hash, 3,  "DLNAClient: skipping this device.";next;}
$hash->{DEV} = $dev;
}
return undef;
}

1;

=pod
=begin html

<a name="DLNAClient"></a>
<h3>DLNAClient</h3>
<ul>

  Define a DLNA client. A DLNA client can take an URL to play via <a href="#set">set</a>.
 
  <br><br>

  <a name="DLNAClientdefine"></a>
  <b>Define</b>
  <ul>
    <code>define &lt;name&gt; DLNAClient &lt;regex&gt;</code>
    <br><br>

    Example:
    <ul>
      <code>define MyPlayer DLNAClient NP2500</code><br>
      Here, NP2500 is part of the name of the player as it announces itself to the network.<br/>
      <code>set MyPlayer http://link-to-my/file.mp3</code><br>
    </ul>
  </ul>
  <br>

  <a name="DLNAClientset"></a>
  <b>Set</b>
  <ul>
    <code>set &lt;name&gt; &lt;value&gt</code><br>
    Set any URL to play.
  </ul>
  <br>

  <a name="DLNAClientget"></a>
  <b>Get</b> <ul>N/A</ul><br>

  <a name="DLNAClientattr"></a>
  <b>Attributes</b>
  <ul>
    <li>server<br>
NOT YET IMPLEMENTED - Name of DLNA Server library to search for given filename
</li>
   
  </ul>
  <br>

</ul>

=end html
=cut


Ein Beispiel:

define MyPlayer DLNAClient NP2500
set MyPlayer http://link-to-my/file.mp3


Grüße,

Kevin

DosiRocker

Hallo,
ich wollte es gerne mal auf einem Raspberry probieren,allerdings weiß ich  Newbie wieder zu wenig wie die Library NET::UPNP installiert wird.
Kann mir jemand helfen?

Dankeschön,
Martin
Cubietruck: CUNO 868;CUL HM
1 Wire: 1 OWAD, 13 OTHERM
10 FS20 ST; 3 HMS100WD; 1 EM1000;  4 S300TH;
4 HM_CC_RT_DN, HM_SEC_SC
AVM 7390 als FHEM2FHEM, Raspberry Pi

Rince

Wenn du CPAN schon installiert hast,

sudo cpan NET::UPNP


In einer ssh Sitzung, nicht in fhem eingeben ;)
Wer zu meinen Posts eine Frage schreibt und auf eine Antwort wartet, ist hiermit herzlich eingeladen mich per PN darauf aufmerksam zu machen. (Bitte mit Link zum betreffenden Thread)

DosiRocker

Zitat von: Rince am 01 Februar 2014, 15:42:45
Wenn du CPAN schon installiert hast,

sudo cpan NET::UPNP


In einer ssh Sitzung, nicht in fhem eingeben ;)

Dankeschön, dann brauche ich nur noch cpan, werde ich mal suchen
Martin
Cubietruck: CUNO 868;CUL HM
1 Wire: 1 OWAD, 13 OTHERM
10 FS20 ST; 3 HMS100WD; 1 EM1000;  4 S300TH;
4 HM_CC_RT_DN, HM_SEC_SC
AVM 7390 als FHEM2FHEM, Raspberry Pi

betateilchen

apt-get install libnet-upnp-perl

viel einfacher und zuverlässiger :)
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

DosiRocker

Zitat von: betateilchen am 03 Februar 2014, 11:01:07
apt-get install libnet-upnp-perl

viel einfacher und zuverlässiger :)

Dankeschön, das schaffe ich dann als Dummy noch eher  ;D
Cubietruck: CUNO 868;CUL HM
1 Wire: 1 OWAD, 13 OTHERM
10 FS20 ST; 3 HMS100WD; 1 EM1000;  4 S300TH;
4 HM_CC_RT_DN, HM_SEC_SC
AVM 7390 als FHEM2FHEM, Raspberry Pi