TV Programm: Unterschied zwischen den Versionen

Aus FHEMWiki
(more channels added)
K (Tippfehler?)
 
(6 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt)
Zeile 87: Zeile 87:
#my $timeAdjust = 86400;
#my $timeAdjust = 86400;


# internal variable
my $timepiece = 0;


my $redt = qr/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(?:\s+([+-]\d{4}))?$/;
eval "use Time::Piece";
 
$timepiece = 1 if (!$@);
 
sub time2xmltv($)
{
  my @t = localtime(shift);
  return sprintf("%04d%02d%02d%02d%02d%02d", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
}


sub xmltv2epoch($)
sub xmltv2epoch($)
{
{
   my $dt = shift;
   my $t = shift;


   if ($dt =~ $redt)
   if (1 == $timepiece)
   {
   {
     if (defined($7))
     return Time::Piece->strptime($t, '%Y%m%d%H%M%S %z')->epoch;
    {
      return $1.'-'.$2.'-'.$3.' '.$4.':'.$5.':'.$6.' '.$7;
    }
    else
    {
      return $1.'-'.$2.'-'.$3.' '.$4.':'.$5.':'.$6;
    }
   }
   }
  else
  {
    substr($t, 8, 0) = 'T';


  return '2000-01-01 00:00:00';
    return str2time($t);
  }
}
}


Zeile 142: Zeile 135:
   my $n = 0;
   my $n = 0;
   my $k = 0;
   my $k = 0;
   my $primeTime = substr(FmtDateTime(time() - $timeAdjust), 0, 11).'20:14:00';
   my $primeTime = substr(FmtDateTime(time() + $timeAdjust), 0, 11).'20:14:00';
   my $sendTelnet = '';
   my $sendTelnet = '';


   if (!$@)
   if (!$@)
   {
   {
     my $old = time2xmltv(time() - $timeAdjust);
     my $old = time() + $timeAdjust;


     foreach (@{forcearray($xml->{'tv'}{'programme'})})
     foreach (@{forcearray($xml->{'tv'}{'programme'})})
Zeile 153: Zeile 146:
       if ($_->{'channel'}{'value'} =~ $channelFilter)
       if ($_->{'channel'}{'value'} =~ $channelFilter)
       {
       {
         my $stop = substr($_->{'stop'}{'value'}, 0, 14);
         my $stop = xmltv2epoch($_->{'stop'}{'value'});


         # filter old stuff
         # filter old stuff
         if (($stop cmp $old) >= 0)
         if ($stop > $old)
         {
         {
           if ($lastChannel ne $_->{'channel'}{'value'})
           if ($lastChannel ne $_->{'channel'}{'value'})
Zeile 171: Zeile 164:
           {
           {
             my $fi = sprintf("%03d", $i);
             my $fi = sprintf("%03d", $i);
             my $start = str2time(xmltv2epoch($_->{'start'}{'value'}));
             my $start = xmltv2epoch($_->{'start'}{'value'});
             my $readingName;
             my $readingName;
             my $readingValue;
             my $readingValue;
            #$stop = str2time(xmltv2epoch($_->{'stop'}{'value'}));


             $readingName = 'next_'.$reading.'_'.$fi.'_bdate';
             $readingName = 'next_'.$reading.'_'.$fi.'_bdate';
Zeile 225: Zeile 216:
           if ($n < 3 && (!defined($mode) || 'prime' eq $mode))
           if ($n < 3 && (!defined($mode) || 'prime' eq $mode))
           {
           {
             my $start = str2time(xmltv2epoch($_->{'start'}{'value'}));
             my $start = xmltv2epoch($_->{'start'}{'value'});
             my $fmtStart = FmtDateTime($start);
             my $fmtStart = FmtDateTime($start);
             my $bdate = substr($fmtStart, 0, 10);
             my $bdate = substr($fmtStart, 0, 10);
Zeile 235: Zeile 226:
               my $readingName;
               my $readingName;
               my $readingValue;
               my $readingValue;
              #$stop = str2time(xmltv2epoch($_->{'stop'}{'value'}));


               $readingName = 'prime_'.$reading.'_'.$fn.'_bdate';
               $readingName = 'prime_'.$reading.'_'.$fn.'_bdate';
Zeile 373: Zeile 362:
   # http://91.121.106.172/~rytecepg/epg_data/rytecDE_SportMovies.xz
   # http://91.121.106.172/~rytecepg/epg_data/rytecDE_SportMovies.xz
   my $output = qx(wget http://www.vuplus-community.net/rytec/rytecDE_Basic.xz -O /opt/fhem/tv/rytecDE_Basic.xz 2>&1);
   my $output = qx(wget http://www.vuplus-community.net/rytec/rytecDE_Basic.xz -O /opt/fhem/tv/rytecDE_Basic.xz 2>&1);
  #my $output = qx(wget http://192.168.178.100:8083/fhem/www/images/guide.xml -O /opt/fhem/tv/rytecDE_Basic 2>&1);
   #print $output;
   #print $output;
   $output = qx(xz -df /opt/fhem/tv/rytecDE_Basic.xz 2>&1);
   $output = qx(xz -df /opt/fhem/tv/rytecDE_Basic.xz 2>&1);
Zeile 441: Zeile 431:


<pre>
<pre>
perl /opt/fhem/tv/tv.pl dmy_TV download
perl /opt/fhem/tv/tv.pl dmy_TV downloadMerge
</pre>
</pre>


Zeile 455: Zeile 445:


<pre>
<pre>
defmod at_TV_DOWNLOAD at *00:10:00 {if ((1 == $wday) || (4 == $wday)) {fhem("perl /opt/fhem/tv/tv.pl dmy_TV downloadMerge")}}
defmod at_TV_DOWNLOAD at *00:10:00 {if ((1 == $wday) || (4 == $wday)) {fhem("\"perl /opt/fhem/tv/tv.pl dmy_TV downloadMerge\"")}}
</pre>
</pre>


Zeile 680: Zeile 670:
== Variante 4 (RSS Daten einlesen): ==
== Variante 4 (RSS Daten einlesen): ==


Zuerst muss ein Service gefunden werden, der RSS zu JSON Daten konvertieren kann. Anbieten würde sich hier z.B. www.rss2json.com. Hier kann man sich kostenlos anmelden und einen API Key generieren.
Die Website texxas.de stellt das aktuelle TV-Programm in einem RSS-Feed bereit, dies erzeugt eine sehr geringe Downloadmenge. Die Website unterteilt das Programm in verschiedene Kategorien:
* Hauptsender (ARD, ZDF, RTL, ProSieben, Sat.1, Kabel1, VOX)
* Spartensender (Anixe HD, ARTE, Bibel TV, Comedy Central, DMAX, HGTV, Kabel Eins Classics, MTV, One)
* Regionalsender
* Dokumentation
* …


=== httpmod Device anlegen: ===
=== rssFeed Device anlegen: ===
 
Das Attribut rfAllReadingsEvents sorgt dafür, dass EPG-Aktualisierung auch zu Events führen in Verbindung mit event-on-change / event-on-update. Siehe dazu commandref zum Modul rssFeed.
Vergesst bitte nicht im unten stehenden Code euren API Key einzutragen!


<pre>
<pre>
defmod TV_JETZT_HAUPTSENDER HTTPMOD https://api.rss2json.com/v1/api.json?rss_url=http%3A%2F%2Fwww.texxas.de%2Ftv%2FhauptsenderJetzt.xml&api_key=<dein API Key>&order_by=pubDate&order_dir=asc&count=100 300
defmod Dev_Multimedia_EPG_Spartensender rssFeed http://www.texxas.de/tv/spartensenderJetzt.xml 300
attr TV_JETZT_HAUPTSENDER userattr get01Name get01URL getEncode readingEncode
attr Dev_Multimedia_EPG_Spartensender rfAllReadingsEvents 1
attr TV_JETZT_HAUPTSENDER disable 1
attr TV_JETZT_HAUPTSENDER extractAllJSON 1
attr TV_JETZT_HAUPTSENDER get01Name update
attr TV_JETZT_HAUPTSENDER get01URL https://api.rss2json.com/v1/api.json?rss_url=http%3A%2F%2Fwww.texxas.de%2Ftv%2FhauptsenderJetzt.xml&api_key=<dein API Key>&order_by=pubDate&order_dir=asc&count=100
attr TV_JETZT_HAUPTSENDER getEncode UTF-8
attr TV_JETZT_HAUPTSENDER readingEncode UTF-8
attr TV_JETZT_HAUPTSENDER timeout 10
</pre>
</pre>


www.texxas.de bietet weitere rss an, die man ebenfalls einbinden könnte. Wie man daraus dann eine readingsGroup erstellt, muss ich leider schuldig bleiben, ich habe diesen Ansatz nicht weiter verfolgt. Funktionieren würde er aber und das Download Volumen ist bei jeder Abfrage nur einige wenige kb.
Anschließend liegen je Sender zwei Readings vor:
 
{| class="wikitable"
|+
!Reading
!Wert
|-
|n01_title
|ARD Alpha: Panoramabilder
|-
|n01_description
|20.11.2022 08:20 - 09:45
|-
|n02_title
|ARTE: 27 - Das europäische Magazin
|-
|n02_description
|20.11.2022 09:30 - 10:15<nowiki><br></nowiki>In Europa ist die Sterbehilfe in gerade einmal vier Ländern erlaubt: in den Niederlanden, Belgien, Luxemburg und seit neuestem auch in Spanien. Die Schweiz hat mit der aktiven Sterbehilfe einen alternativen Weg gewählt. In Frankreich hat die nationale Ethikkommission das Thema im September wieder auf den Tisch gebracht und sich für eine "streng geregelte" Form der Sterbehilfe ausgesprochen. Wie sollten kranke Menschen mit Sterbewillen begleitet werden? Unter welchen Umständen ist die akt ...
|}
[[Datei:screenshot-2018-04-28-16-28-03.png|200px|thumb|left|Beispiel]]
[[Datei:screenshot-2018-04-28-16-28-03.png|200px|thumb|left|Beispiel]]


[[Kategorie:Code Snippets]]
[[Kategorie:Code Snippets]]

Aktuelle Version vom 20. November 2022, 16:44 Uhr

Sich das aktuelle Fernsehprogramm in FHEM anzeigen zu lassen, ist leider gar nicht so einfach. Die einzelnen Anbieter stellen keine entsprechenden Schnittstellen zur Verfügung, um auf einfache Art und Weise das aktuelle Fernsehprogramm für beliebige Sender einlesen und darstellen zu können. Mit ein paar Tricks funktioniert es aber trotzdem.

Hierfür gibt es gleich mehrere Ansätze:

  • Im einfachsten Fall bindet man sich ein iframe in die FHEM Weboberfläche ein. Einige Anbieter bieten sogar einen personalisierten Zugriff, so das man sich eine Übersicht nur mit den gewünschten Sendern zusammen stellen kann.
  • Es besteht die Möglichkeit EPG Daten in einem speziellen xmltv Format in die FHEM Installation zu laden (enthält das Programm für 6-7 Tage), diese XML Datei zu parsen und dann in FHEM z.B. über eine readingsGroup darzustellen.
  • Für Linux und auch Windows sind Tools verfügbar, mit denen man die Seiten von verschiedenen TV Programmanbietern grabben und daraus eine XML Datei im xmltv Format erstellen kann (enthält das Programm von 1-14 Tagen je nach Anbieter). Einer der bekanntesten Vertreter ist WebGrab++. Diese XML Datei kann dann wieder in FHEM eingelesen und angezeigt werden z.B. über eine readingsGroup. Das Parsen der Daten ist mit einem sehr hohen Traffic verbunden, so das hier die vorher erwähnten Methoden vorzuziehen sind.
  • Es gibt Webseiten, die das TV Programm als rss zur Verfügung stellen. Darüber hinaus gibt es Services die online rss zu json konvertieren. Kombiniert man nun beides, dann kann man mit httpmod das Ganze mit wenigen Zeilen einlesen.
  • Über httpmod könnte man eine Webseite eines TV Programmanbieters zyklisch einlesen und die für FHEM notwendigen Daten extrahieren. Von dieser Methode ist dringend abzuraten, da diese einen enormen Traffic sowohl für einen selbst, als auch für den Anbieter bedeutet. Aus diesem Grund soll diese Methode hier auch nicht dargestellt werden.

Variante 1 (iframe):

define wl_TV weblink iframe <Webseite des TV Programmanbieters z.B. http://www.klack.de/fernsehprogramm/was-laeuft-gerade/0/0/all.html>
attr wl_TV htmlattr width="1024" height="768"

Das Attribut legt die Größe des iframes fest und kann beliebig angepasst werden.

Variante 2 (Download der EPG Daten):

Dieser Ansatz ist bereits etwas komplizierter, aber immer noch sehr einfach einzubinden.

Vorbereitungen:

Fehlende Perl Module installieren:

sudo apt-get install libxml-bare-perl libdatetime-perl wget xz-utils

Die ersten beiden Bibliotheken werden benötigt, um die XML Datei zu parsen. wget wird benötigt um die Datei zu downloaden und xz enthält den Unpacker für die runtergeladene Datei.

Pfad für den Download anlegen und mit den entsprechenden Rechten versehen:

sudo mkdir /opt/fhem/tv
sudo chown fhem:dialout /opt/fhem/tv

In diesem Verzeichnis soll später die mit wget runtergeladene XML Datei landen.

99_myUtils.pm erweitern:

Dieser Code kann einfach in die Zwischenablage kopiert und in die Datei 99_myUtils.pm eingefügt werden.

sub rgUnfold($$)
{
  my ($device, $reading) = @_;
  my $title = ReadingsVal($device, $reading.'title', 'na');
  my $desc = ReadingsVal($device, $reading.'stitle', 'na')."\n\n".
             ReadingsVal($device, $reading.'desc', 'na');

  $title =~ s/(.{1,45}|\S{46,})(?:\s[^\S\r\n]*|\Z)/$1<br>/g;
  $desc =~ s/<br>/\n/g;
  $desc =~ s/(.{1,65}|\S{66,})(?:\s[^\S\r\n]*|\Z)/$1<br>/g; 
  $desc =~ s/[\r\'\"]/ /g;
  $desc =~ s/[\n]|\\n/<br>/g;
  return "<a href=\"#!\" onclick=\"FW_okDialog('".$desc."')\">".$title."</a>";
}

Dummy Device anlegen:

Dieses Device dient zur Datenhaltung. Hier werden immer die nächsten 3 Sendungen und die 3 Primetime Sendungen des aktuellen Tages als Readings abgelegt.

define dmy_TV dummy

Perl Script einrichten:

Den folgenden Code in die Datei tv.pl kopieren und in den Ordner /opt/fhem/tv/tv.pl kopieren:

#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Date::Parse;
use Encode qw(encode_utf8 decode_utf8);
use XML::Bare 0.53 qw(forcearray);
use Data::Dumper;

my $channelFilter = qr/^(?:ARD\.|ZDF\.|Sat1\.|RTL\.|RTL2\.|Pro7\.|DMax\.|Vox\.|Kabel\.|KabelEinsClassic\.|KabelEinsDoku\.|ntv\.|ProSiebenMaxx\.|Sixx\.|TLC\.|N24Doku\.|SonyEntertainmentTV\.|AandE\.|TNTSerie\.|AnimalPlanet\.|History\.|Kinowelt\.|NatGeoHD\.|PLANET\.|Silverline\.|13thStreet\.|AXN\.|SciFi\.|TNTFilm\.)/;
my $timeAdjust = 0;

#my $channelFilter = qr/^(?:ARD|ZDF$|SAT\.1|RTL$|RTL II|PRO 7|DMAX|VOX|KABEL 1|13TH STREET|ANIMAL PLANET|Silverline|TNT Film|N24|kabel eins classics|ProSieben MAXX|Syfy|AE|TLC|AXN|sixx|Kinowelt TV|History)/;
#my $timeAdjust = 86400;

# internal variable
my $timepiece = 0;

eval "use Time::Piece";
$timepiece = 1 if (!$@);

sub xmltv2epoch($)
{
  my $t = shift;

  if (1 == $timepiece)
  {
    return Time::Piece->strptime($t, '%Y%m%d%H%M%S %z')->epoch;
  }
  else
  {
    substr($t, 8, 0) = 'T';

    return str2time($t);
  }
}

sub FmtDateTime($)
{
  my @t = localtime(shift);
  return sprintf("%04d-%02d-%02d %02d:%02d:%02d", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
}

sub filterText($)
{
  my $text = shift;

  $text =~ s/["`;'\r]//g;
  $text =~ s/[\n]/<br>/g;

  return $text;
}

sub tvParse($;$)
{
  my ($device, $mode) = @_;
  my $obj = XML::Bare->new(file => '/opt/fhem/tv/rytecDE_Basic');
  my $xml = $obj->parse();
  my $lastChannel = '';
  my $reading = '';
  my $i = 0;
  my $n = 0;
  my $k = 0;
  my $primeTime = substr(FmtDateTime(time() + $timeAdjust), 0, 11).'20:14:00';
  my $sendTelnet = '';

  if (!$@)
  {
    my $old = time() + $timeAdjust;

    foreach (@{forcearray($xml->{'tv'}{'programme'})})
    {
      if ($_->{'channel'}{'value'} =~ $channelFilter)
      {
        my $stop = xmltv2epoch($_->{'stop'}{'value'});

        # filter old stuff
        if ($stop > $old)
        {
          if ($lastChannel ne $_->{'channel'}{'value'})
          {
            $lastChannel = $_->{'channel'}{'value'};
            $reading = $_->{'channel'}{'value'};
            $reading =~ s/[\.\s]//g;
            $reading =~ s/de$//;
            $i = 0;
            $n = 0;
          }

          if ($i < 3 && (!defined($mode) || 'next' eq $mode))
          {
            my $fi = sprintf("%03d", $i);
            my $start = xmltv2epoch($_->{'start'}{'value'});
            my $readingName;
            my $readingValue;

            $readingName = 'next_'.$reading.'_'.$fi.'_bdate';
            $readingValue = substr(FmtDateTime($start), 0, 10);
            $sendTelnet .= ";setreading $device $readingName $readingValue";

            $readingName = 'next_'.$reading.'_'.$fi.'_btime';
            $readingValue = substr(FmtDateTime($start), 11, 8);
            $sendTelnet .= ";setreading $device $readingName $readingValue";

            #$readingName = 'next_'.$reading.'_'.$fi.'_edate';
            #$readingValue = substr(FmtDateTime($stop), 0, 10);
            #$sendTelnet .= ";setreading $device $readingName $readingValue";

            #$readingName = 'next_'.$reading.'_'.$fi.'_etime';
            #$readingValue = substr(FmtDateTime($stop), 11, 8);
            #$sendTelnet .= ";setreading $device $readingName $readingValue";

            $readingName = 'next_'.$reading.'_'.$fi.'_title';
            $readingValue = filterText(@{forcearray($_->{'title'})}[0]->{'value'});
            $sendTelnet .= ";setreading $device $readingName $readingValue";

            $readingName = 'next_'.$reading.'_'.$fi.'_stitle';
            if (exists($_->{'sub-title'}{'value'}))
            {
              $readingValue = filterText($_->{'sub-title'}{'value'});
            }
            else
            {
              $readingValue = 'na';
            }
            $sendTelnet .= ";setreading $device $readingName $readingValue";

            $readingName = 'next_'.$reading.'_'.$fi.'_desc';
            if (exists($_->{'desc'}{'value'}))
            {
              $readingValue = filterText($_->{'desc'}{'value'});
            }
            else
            {
              $readingValue = 'na';
            }
            $sendTelnet .= ";setreading $device $readingName $readingValue";

            $i++;
            $k++;
          }

          if ($n < 3 && (!defined($mode) || 'prime' eq $mode))
          {
            my $start = xmltv2epoch($_->{'start'}{'value'});
            my $fmtStart = FmtDateTime($start);
            my $bdate = substr($fmtStart, 0, 10);
            my $btime = substr($fmtStart, 11, 8);

            if ($bdate.' '.$btime gt $primeTime)
            {
              my $fn = sprintf("%03d", $n);
              my $readingName;
              my $readingValue;

              $readingName = 'prime_'.$reading.'_'.$fn.'_bdate';
              $readingValue = substr(FmtDateTime($start), 0, 10);
              $sendTelnet .= ";setreading $device $readingName $readingValue";

              $readingName = 'prime_'.$reading.'_'.$fn.'_btime';
              $readingValue = substr(FmtDateTime($start), 11, 8);
              $sendTelnet .= ";setreading $device $readingName $readingValue";

              #$readingName = 'prime_'.$reading.'_'.$fn.'_edate';
              #$readingValue = substr(FmtDateTime($stop), 0, 10);
              #$sendTelnet .= ";setreading $device $readingName $readingValue";

              #$readingName = 'prime_'.$reading.'_'.$fn.'_etime';
              #$readingValue = substr(FmtDateTime($stop), 11, 8);
              #$sendTelnet .= ";setreading $device $readingName $readingValue";

              $readingName = 'prime_'.$reading.'_'.$fn.'_title';
              $readingValue = filterText(@{forcearray($_->{'title'})}[0]->{'value'});
              $sendTelnet .= ";setreading $device $readingName $readingValue";

              $readingName = 'prime_'.$reading.'_'.$fn.'_stitle';
              if (exists($_->{'sub-title'}{'value'}))
              {
                $readingValue = filterText($_->{'sub-title'}{'value'});
              }
              else
              {
                $readingValue = 'na';
              }
              $sendTelnet .= ";setreading $device $readingName $readingValue";

              $readingName = 'prime_'.$reading.'_'.$fn.'_desc';
              if (exists($_->{'desc'}{'value'}))
              {
                $readingValue = filterText($_->{'desc'}{'value'});
              }
              else
              {
                $readingValue = 'na';
              }
              $sendTelnet .= ";setreading $device $readingName $readingValue";

              $n++;
              $k++;
            }
          }

          if ($k >= 10)
          {
            #system('/opt/fhem/fhem.pl 7072 "'.$sendTelnet.'"');
            my $result = `perl /opt/fhem/fhem.pl 7072 "$sendTelnet"`;

            $k = 0;
            $sendTelnet = '';
          }
        }
      }
    }

    if ('' ne $sendTelnet)
    {
      #system('/opt/fhem/fhem.pl 7072 "'.$sendTelnet.'"');
      my $result = `perl /opt/fhem/fhem.pl 7072 "$sendTelnet"`;
    }
  }
}

sub tvMerge($$)
{
  my ($dstName, $srcName) = @_;
  my $fh;
  my $dst;
  my $src;
  my $start = '';
  my $channels1 = '';
  my $channels2 = '';
  my $programms1 = '';
  my $programms2 = '';
  my $end = '';

  open($fh, '<', $dstName) or die "Can't open file $!";
  read($fh, $dst, -s $fh);
  close($fh);

  open($fh, '<', $srcName) or die "Can't open file $!";
  read($fh, $src, -s $fh);
  close($fh);

  if ($dst =~ /^(.*?)<channel/s)
  {
    $start = $1;
  }

  if ($dst =~ /<\/programme>(?!.*<\/programme>)(.*)$/s)
  {
    $end = $1;
  }

  while ($dst =~ /(\s*<channel\s.*?<\/channel>)/sg)
  {
    $channels1 .= $1;
  }

  while ($dst =~ /(\s*<programme\s.*?<\/programme>)/sg)
  {
    $programms1 .= $1;
  }

  while ($src =~ /(\s*<channel\s.*?<\/channel>)/sg)
  {
    $channels2 .= $1;
  }

  while ($src =~ /(\s*<programme\s.*?<\/programme>)/sg)
  {
    $programms2 .= $1;
  }

  open($fh, '>', $dstName) or die "Can't open file $!";
  print $fh $start.$channels1.$channels2.$programms1.$programms2.$end;
  close($fh);
}

sub tvDownload()
{
  # other server
  # http://www.xmltvepg.nl/rytecDE_Basic.xz
  # http://91.121.106.172/~rytecepg/epg_data/rytecDE_Basic.xz
  # http://www.vuplus-community.net/rytec/rytecDE_Common.xz
  # http://www.xmltvepg.nl/rytecDE_Common.xz
  # http://91.121.106.172/~rytecepg/epg_data/rytecDE_Common.xz
  # http://www.vuplus-community.net/rytec/rytecDE_SportMovies.xz
  # http://www.xmltvepg.nl/rytecDE_SportMovies.xz
  # http://91.121.106.172/~rytecepg/epg_data/rytecDE_SportMovies.xz
  my $output = qx(wget http://www.vuplus-community.net/rytec/rytecDE_Basic.xz -O /opt/fhem/tv/rytecDE_Basic.xz 2>&1);
  #my $output = qx(wget http://192.168.178.100:8083/fhem/www/images/guide.xml -O /opt/fhem/tv/rytecDE_Basic 2>&1);
  #print $output;
  $output = qx(xz -df /opt/fhem/tv/rytecDE_Basic.xz 2>&1);
  #print $output;
}

sub tvDownloadMerge()
{
  # other server
  # http://www.xmltvepg.nl/rytecDE_Basic.xz
  # http://91.121.106.172/~rytecepg/epg_data/rytecDE_Basic.xz
  # http://www.vuplus-community.net/rytec/rytecDE_Common.xz
  # http://www.xmltvepg.nl/rytecDE_Common.xz
  # http://91.121.106.172/~rytecepg/epg_data/rytecDE_Common.xz
  # http://www.vuplus-community.net/rytec/rytecDE_SportMovies.xz
  # http://www.xmltvepg.nl/rytecDE_SportMovies.xz
  # http://91.121.106.172/~rytecepg/epg_data/rytecDE_SportMovies.xz
  my $output = qx(wget http://www.vuplus-community.net/rytec/rytecDE_Basic.xz -O /opt/fhem/tv/rytecDE_Basic.xz 2>&1);
  #print $output;
  $output = qx(xz -df /opt/fhem/tv/rytecDE_Basic.xz 2>&1);
  #print $output;
  $output = qx(wget http://www.vuplus-community.net/rytec/rytecDE_Common.xz -O /opt/fhem/tv/rytecDE_Common.xz 2>&1);
  #print $output;
  $output = qx(xz -df /opt/fhem/tv/rytecDE_Common.xz 2>&1);
  #print $output;
  tvMerge('/opt/fhem/tv/rytecDE_Basic', '/opt/fhem/tv/rytecDE_Common');
}


my $d = shift || die "Need a device!\n";
my $m = shift || die "Need a mode!\n";

# mode 'parse': update next and prime
# mode 'next' : update next only
# mode 'prime': update prime only
if ('download' eq $m)
{
  tvDownload();
}
elsif ('downloadMerge' eq $m)
{
  tvDownloadMerge();
}
elsif ('parse' eq $m)
{
  tvParse($d);
}
else
{
  tvParse($d, $m);
}

exit;


Sowohl das Verzeichnis, als auch das Script selbst muss mit den entsprechenden Rechten versehen werden:

sudo chmod 744 /opt/fhem/tv/tv.pl
sudo chown fhem:dialout /opt/fhem/tv/tv.pl

Bei Bedarf kann nun innerhalb des Scriptes die Senderliste angepasst werden.

Jetzt muss der Download einmalig von der Konsole (nicht mit der FHEM Befehlszeile verwechseln!) gestartet werden, da man ansonsten mehrere Tage warten muss, bis alles funktioniert:

perl /opt/fhem/tv/tv.pl dmy_TV downloadMerge

Danach muss die entstandene Datei /opt/fhem/tv/rytecDE_Basic mit den richtigen Rechten ausgestattet werden:

sudo chown fhem:dialout /opt/fhem/tv/rytecDE_Basic

at Devices anlegen:

3 "at" Devices müssen angelegt werden. Eins für den Download (alle 3 Tage 1x), eins für das Parsen der aktuellen Daten ins Dummy Device (alle 15 Minuten) und eins für das Parsen der Primtime Sendungen (einmal am Tag). Die 3 "at" sind als raw Definitionen kopiert und können auch als solche wieder angelegt werden. Dazu irgend ein Device öffnen, ganz unten auf "Raw definition" klicken und alles entfernen. Den Code von hier einfügen und ausführen und die Devices sind angelegt. Jedes at Device muss separat angelegt werden!

defmod at_TV_DOWNLOAD at *00:10:00 {if ((1 == $wday) || (4 == $wday)) {fhem("\"perl /opt/fhem/tv/tv.pl dmy_TV downloadMerge\"")}}
defmod at_TV_UPDATE at +*00:15:00 "perl /opt/fhem/tv/tv.pl dmy_TV next"
defmod at_TV_UPDATE_PRIME at *00:15:00 "perl /opt/fhem/tv/tv.pl dmy_TV prime"

Damit sind die Vorbereitungen abgeschlossen!

Dummy Device mit Daten füllen:

Dieser Vorgang muss nur einmalig gemacht werden, damit sofort Ergebnisse sichtbar sind und man nicht erst 15 Minuten warten muss.

set at_TV_UPDATE execNow

readingsGroups anlegen:

Zuletzt müssen wir uns noch 2 readingsGroups anlegen, damit das aktuelle TV Programm in FHEM auch dargestellt werden kann. Die readingsGroups müssen, wie zuvor die at Devices, als Raw Import importiert werden!:

Aktuelle Sendungen:

defmod rg_TV readingsGroup <Sender>,<ab>,<Aktuelle Sendung>,<|>,<ab>,<Sendung>,<|>,<ab>,<Sendung>\
dmy_TV:<%tv/ard>,next_ARD_000_btime,<{rgUnfold($DEVICE,'next_ARD_000_')}@next_ARD_000_title>,<|>,next_ARD_001_btime,<{rgUnfold($DEVICE,'next_ARD_001_')}@next_ARD_001_title>,<|>,next_ARD_002_btime,<{rgUnfold($DEVICE,'next_ARD_002_')}@next_ARD_002_title>\
dmy_TV:<%tv/zdf>,next_ZDF_000_btime,<{rgUnfold($DEVICE,'next_ZDF_000_')}@next_ZDF_000_title>,<|>,next_ZDF_001_btime,<{rgUnfold($DEVICE,'next_ZDF_001_')}@next_ZDF_001_title>,<|>,next_ZDF_002_btime,<{rgUnfold($DEVICE,'next_ZDF_002_')}@next_ZDF_002_title>\
dmy_TV:<%tv/sat1>,next_Sat1_000_btime,<{rgUnfold($DEVICE,'next_Sat1_000_')}@next_Sat1_000_title>,<|>,next_Sat1_001_btime,<{rgUnfold($DEVICE,'next_Sat1_001_')}@next_Sat1_001_title>,<|>,next_Sat1_002_btime,<{rgUnfold($DEVICE,'next_Sat1_002_')}@next_Sat1_002_title>\
dmy_TV:<%tv/rtl>,next_RTL_000_btime,<{rgUnfold($DEVICE,'next_RTL_000_')}@next_RTL_000_title>,<|>,next_RTL_001_btime,<{rgUnfold($DEVICE,'next_RTL_001_')}@next_RTL_001_title>,<|>,next_RTL_002_btime,<{rgUnfold($DEVICE,'next_RTL_002_')}@next_RTL_002_title>\
dmy_TV:<%tv/rtl2>,next_RTL2_000_btime,<{rgUnfold($DEVICE,'next_RTL2_000_')}@next_RTL2_000_title>,<|>,next_RTL2_001_btime,<{rgUnfold($DEVICE,'next_RTL2_001_')}@next_RTL2_001_title>,<|>,next_RTL2_002_btime,<{rgUnfold($DEVICE,'next_RTL2_002_')}@next_RTL2_002_title>\
dmy_TV:<%tv/pro7>,next_Pro7_000_btime,<{rgUnfold($DEVICE,'next_Pro7_000_')}@next_Pro7_000_title>,<|>,next_Pro7_001_btime,<{rgUnfold($DEVICE,'next_Pro7_001_')}@next_Pro7_001_title>,<|>,next_Pro7_002_btime,<{rgUnfold($DEVICE,'next_Pro7_002_')}@next_Pro7_002_title>\
dmy_TV:<%tv/dmax>,next_DMax_000_btime,<{rgUnfold($DEVICE,'next_DMax_000_')}@next_DMax_000_title>,<|>,next_DMax_001_btime,<{rgUnfold($DEVICE,'next_DMax_001_')}@next_DMax_001_title>,<|>,next_DMax_002_btime,<{rgUnfold($DEVICE,'next_DMax_002_')}@next_DMax_002_title>\
dmy_TV:<%tv/vox>,next_Vox_000_btime,<{rgUnfold($DEVICE,'next_Vox_000_')}@next_Vox_000_title>,<|>,next_Vox_001_btime,<{rgUnfold($DEVICE,'next_Vox_001_')}@next_Vox_001_title>,<|>,next_Vox_002_btime,<{rgUnfold($DEVICE,'next_Vox_002_')}@next_Vox_002_title>\
dmy_TV:<%tv/kabel1>,next_Kabel_000_btime,<{rgUnfold($DEVICE,'next_Kabel_000_')}@next_Kabel_000_title>,<|>,next_Kabel_001_btime,<{rgUnfold($DEVICE,'next_Kabel_001_')}@next_Kabel_001_title>,<|>,next_Kabel_002_btime,<{rgUnfold($DEVICE,'next_Kabel_002_')}@next_Kabel_002_title>\
dmy_TV:<%tv/kabel1classic>,next_KabelEinsClassic_000_btime,<{rgUnfold($DEVICE,'next_KabelEinsClassic_000_')}@next_KabelEinsClassic_000_title>,<|>,next_KabelEinsClassic_001_btime,<{rgUnfold($DEVICE,'next_KabelEinsClassic_001_')}@next_KabelEinsClassic_001_title>,<|>,next_KabelEinsClassic_002_btime,<{rgUnfold($DEVICE,'next_KabelEinsClassic_002_')}@next_KabelEinsClassic_002_title>\
dmy_TV:<%tv/13thstreet>,next_13thStreet_000_btime,<{rgUnfold($DEVICE,'next_13thStreet_000_')}@next_13thStreet_000_title>,<|>,next_13thStreet_001_btime,<{rgUnfold($DEVICE,'next_13thStreet_001_')}@next_13thStreet_001_title>,<|>,next_13thStreet_002_btime,<{rgUnfold($DEVICE,'next_13thStreet_002_')}@next_13thStreet_002_title>\
dmy_TV:<%tv/silverline>,next_Silverline_000_btime,<{rgUnfold($DEVICE,'next_Silverline_000_')}@next_Silverline_000_title>,<|>,next_Silverline_001_btime,<{rgUnfold($DEVICE,'next_Silverline_001_')}@next_Silverline_001_title>,<|>,next_Silverline_002_btime,<{rgUnfold($DEVICE,'next_Silverline_002_')}@next_Silverline_002_title>\
dmy_TV:<%tv/tntfilm>,next_TNTFilm_000_btime,<{rgUnfold($DEVICE,'next_TNTFilm_000_')}@next_TNTFilm_000_title>,<|>,next_TNTFilm_001_btime,<{rgUnfold($DEVICE,'next_TNTFilm_001_')}@next_TNTFilm_001_title>,<|>,next_TNTFilm_002_btime,<{rgUnfold($DEVICE,'next_TNTFilm_002_')}@next_TNTFilm_002_title>\
dmy_TV:<%tv/axn>,next_AXN_000_btime,<{rgUnfold($DEVICE,'next_AXN_000_')}@next_AXN_000_title>,<|>,next_AXN_001_btime,<{rgUnfold($DEVICE,'next_AXN_001_')}@next_AXN_001_title>,<|>,next_AXN_002_btime,<{rgUnfold($DEVICE,'next_AXN_002_')}@next_AXN_002_title>\
dmy_TV:<%tv/sonytv>,next_SonyEntertainmentTV_000_btime,<{rgUnfold($DEVICE,'next_SonyEntertainmentTV_000_')}@next_SonyEntertainmentTV_000_title>,<|>,next_SonyEntertainmentTV_001_btime,<{rgUnfold($DEVICE,'next_SonyEntertainmentTV_001_')}@next_SonyEntertainmentTV_001_title>,<|>,next_SonyEntertainmentTV_002_btime,<{rgUnfold($DEVICE,'next_SonyEntertainmentTV_002_')}@next_SonyEntertainmentTV_002_title>\
dmy_TV:<%tv/kinowelt>,next_Kinowelt_000_btime,<{rgUnfold($DEVICE,'next_Kinowelt_000_')}@next_Kinowelt_000_title>,<|>,next_Kinowelt_001_btime,<{rgUnfold($DEVICE,'next_Kinowelt_001_')}@next_Kinowelt_001_title>,<|>,next_Kinowelt_002_btime,<{rgUnfold($DEVICE,'next_Kinowelt_002_')}@next_Kinowelt_002_title>\
dmy_TV:<%tv/pro7maxx>,next_ProSiebenMaxx_000_btime,<{rgUnfold($DEVICE,'next_ProSiebenMaxx_000_')}@next_ProSiebenMaxx_000_title>,<|>,next_ProSiebenMaxx_001_btime,<{rgUnfold($DEVICE,'next_ProSiebenMaxx_001_')}@next_ProSiebenMaxx_001_title>,<|>,next_ProSiebenMaxx_002_btime,<{rgUnfold($DEVICE,'next_ProSiebenMaxx_002_')}@next_ProSiebenMaxx_002_title>\
dmy_TV:<%tv/sixx>,next_Sixx_000_btime,<{rgUnfold($DEVICE,'next_Sixx_000_')}@next_Sixx_000_title>,<|>,next_Sixx_001_btime,<{rgUnfold($DEVICE,'next_Sixx_001_')}@next_Sixx_001_title>,<|>,next_Sixx_002_btime,<{rgUnfold($DEVICE,'next_Sixx_002_')}@next_Sixx_002_title>\
dmy_TV:<%tv/tntserie>,next_TNTSerie_000_btime,<{rgUnfold($DEVICE,'next_TNTSerie_000_')}@next_TNTSerie_000_title>,<|>,next_TNTSerie_001_btime,<{rgUnfold($DEVICE,'next_TNTSerie_001_')}@next_TNTSerie_001_title>,<|>,next_TNTSerie_002_btime,<{rgUnfold($DEVICE,'next_TNTSerie_002_')}@next_TNTSerie_002_title>\
dmy_TV:<%tv/syfy>,next_SciFi_000_btime,<{rgUnfold($DEVICE,'next_SciFi_000_')}@next_SciFi_000_title>,<|>,next_SciFi_001_btime,<{rgUnfold($DEVICE,'next_SciFi_001_')}@next_SciFi_001_title>,<|>,next_SciFi_002_btime,<{rgUnfold($DEVICE,'next_SciFi_002_')}@next_SciFi_002_title>\
dmy_TV:<%tv/ntv>,next_ntv_000_btime,<{rgUnfold($DEVICE,'next_ntv_000_')}@next_ntv_000_title>,<|>,next_ntv_001_btime,<{rgUnfold($DEVICE,'next_ntv_001_')}@next_ntv_001_title>,<|>,next_ntv_002_btime,<{rgUnfold($DEVICE,'next_ntv_002_')}@next_ntv_002_title>\
dmy_TV:<%tv/n24>,next_N24Doku_000_btime,<{rgUnfold($DEVICE,'next_N24Doku_000_')}@next_N24Doku_000_title>,<|>,next_N24Doku_001_btime,<{rgUnfold($DEVICE,'next_N24Doku_001_')}@next_N24Doku_001_title>,<|>,next_N24Doku_002_btime,<{rgUnfold($DEVICE,'next_N24Doku_002_')}@next_N24Doku_002_title>\
dmy_TV:<%tv/history>,next_History_000_btime,<{rgUnfold($DEVICE,'next_History_000_')}@next_History_000_title>,<|>,next_History_001_btime,<{rgUnfold($DEVICE,'next_History_001_')}@next_History_001_title>,<|>,next_History_002_btime,<{rgUnfold($DEVICE,'next_History_002_')}@next_History_002_title>\
dmy_TV:<%tv/planet>,next_PLANET_000_btime,<{rgUnfold($DEVICE,'next_PLANET_000_')}@next_PLANET_000_title>,<|>,next_PLANET_001_btime,<{rgUnfold($DEVICE,'next_PLANET_001_')}@next_PLANET_001_title>,<|>,next_PLANET_002_btime,<{rgUnfold($DEVICE,'next_PLANET_002_')}@next_PLANET_002_title>\
dmy_TV:<%tv/kabel1doku>,next_KabelEinsDoku_000_btime,<{rgUnfold($DEVICE,'next_KabelEinsDoku_000_')}@next_KabelEinsDoku_000_title>,<|>,next_KabelEinsDoku_001_btime,<{rgUnfold($DEVICE,'next_KabelEinsDoku_001_')}@next_KabelEinsDoku_001_title>,<|>,next_KabelEinsDoku_002_btime,<{rgUnfold($DEVICE,'next_KabelEinsDoku_002_')}@next_KabelEinsDoku_002_title>\
dmy_TV:<%tv/animalplanet>,next_AnimalPlanet_000_btime,<{rgUnfold($DEVICE,'next_AnimalPlanet_000_')}@next_AnimalPlanet_000_title>,<|>,next_AnimalPlanet_001_btime,<{rgUnfold($DEVICE,'next_AnimalPlanet_001_')}@next_AnimalPlanet_001_title>,<|>,next_AnimalPlanet_002_btime,<{rgUnfold($DEVICE,'next_AnimalPlanet_002_')}@next_AnimalPlanet_002_title>\
dmy_TV:<%tv/natgeo>,next_NatGeoHD_000_btime,<{rgUnfold($DEVICE,'next_NatGeoHD_000_')}@next_NatGeoHD_000_title>,<|>,next_NatGeoHD_001_btime,<{rgUnfold($DEVICE,'next_NatGeoHD_001_')}@next_NatGeoHD_001_title>,<|>,next_NatGeoHD_002_btime,<{rgUnfold($DEVICE,'next_NatGeoHD_002_')}@next_NatGeoHD_002_title>\
dmy_TV:<%tv/tlc>,next_TLC_000_btime,<{rgUnfold($DEVICE,'next_TLC_000_')}@next_TLC_000_title>,<|>,next_TLC_001_btime,<{rgUnfold($DEVICE,'next_TLC_001_')}@next_TLC_001_title>,<|>,next_TLC_002_btime,<{rgUnfold($DEVICE,'next_TLC_002_')}@next_TLC_002_title>\
dmy_TV:<%tv/ae>,next_AandE_000_btime,<{rgUnfold($DEVICE,'next_AandE_000_')}@next_AandE_000_title>,<|>,next_AandE_001_btime,<{rgUnfold($DEVICE,'next_AandE_001_')}@next_AandE_001_title>,<|>,next_AandE_002_btime,<{rgUnfold($DEVICE,'next_AandE_002_')}@next_AandE_002_title>
attr rg_TV alias Aktuelles TV-Programm
attr rg_TV cellStyle { \
  'r:1,c:1' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:2' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:3' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:5' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:6' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:8' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:9' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"'\
}
attr rg_TV group TV Programm
attr rg_TV nonames 1
attr rg_TV style style="font-size:16px;;"

Primetime Sendungen:

defmod rg_TV_PRIME readingsGroup <Sender>,<ab>,<Sendung>,<|>,<ab>,<Sendung>,<|>,<ab>,<Sendung>\
dmy_TV:<%tv/ard>,prime_ARD_000_btime,<{rgUnfold($DEVICE,'prime_ARD_000_')}@prime_ARD_000_title>,<|>,prime_ARD_001_btime,<{rgUnfold($DEVICE,'prime_ARD_001_')}@prime_ARD_001_title>,<|>,prime_ARD_002_btime,<{rgUnfold($DEVICE,'prime_ARD_002_')}@prime_ARD_002_title>\
dmy_TV:<%tv/zdf>,prime_ZDF_000_btime,<{rgUnfold($DEVICE,'prime_ZDF_000_')}@prime_ZDF_000_title>,<|>,prime_ZDF_001_btime,<{rgUnfold($DEVICE,'prime_ZDF_001_')}@prime_ZDF_001_title>,<|>,prime_ZDF_002_btime,<{rgUnfold($DEVICE,'prime_ZDF_002_')}@prime_ZDF_002_title>\
dmy_TV:<%tv/sat1>,prime_Sat1_000_btime,<{rgUnfold($DEVICE,'prime_Sat1_000_')}@prime_Sat1_000_title>,<|>,prime_Sat1_001_btime,<{rgUnfold($DEVICE,'prime_Sat1_001_')}@prime_Sat1_001_title>,<|>,prime_Sat1_002_btime,<{rgUnfold($DEVICE,'prime_Sat1_002_')}@prime_Sat1_002_title>\
dmy_TV:<%tv/rtl>,prime_RTL_000_btime,<{rgUnfold($DEVICE,'prime_RTL_000_')}@prime_RTL_000_title>,<|>,prime_RTL_001_btime,<{rgUnfold($DEVICE,'prime_RTL_001_')}@prime_RTL_001_title>,<|>,prime_RTL_002_btime,<{rgUnfold($DEVICE,'prime_RTL_002_')}@prime_RTL_002_title>\
dmy_TV:<%tv/rtl2>,prime_RTL2_000_btime,<{rgUnfold($DEVICE,'prime_RTL2_000_')}@prime_RTL2_000_title>,<|>,prime_RTL2_001_btime,<{rgUnfold($DEVICE,'prime_RTL2_001_')}@prime_RTL2_001_title>,<|>,prime_RTL2_002_btime,<{rgUnfold($DEVICE,'prime_RTL2_002_')}@prime_RTL2_002_title>\
dmy_TV:<%tv/pro7>,prime_Pro7_000_btime,<{rgUnfold($DEVICE,'prime_Pro7_000_')}@prime_Pro7_000_title>,<|>,prime_Pro7_001_btime,<{rgUnfold($DEVICE,'prime_Pro7_001_')}@prime_Pro7_001_title>,<|>,prime_Pro7_002_btime,<{rgUnfold($DEVICE,'prime_Pro7_002_')}@prime_Pro7_002_title>\
dmy_TV:<%tv/dmax>,prime_DMax_000_btime,<{rgUnfold($DEVICE,'prime_DMax_000_')}@prime_DMax_000_title>,<|>,prime_DMax_001_btime,<{rgUnfold($DEVICE,'prime_DMax_001_')}@prime_DMax_001_title>,<|>,prime_DMax_002_btime,<{rgUnfold($DEVICE,'prime_DMax_002_')}@prime_DMax_002_title>\
dmy_TV:<%tv/vox>,prime_Vox_000_btime,<{rgUnfold($DEVICE,'prime_Vox_000_')}@prime_Vox_000_title>,<|>,prime_Vox_001_btime,<{rgUnfold($DEVICE,'prime_Vox_001_')}@prime_Vox_001_title>,<|>,prime_Vox_002_btime,<{rgUnfold($DEVICE,'prime_Vox_002_')}@prime_Vox_002_title>\
dmy_TV:<%tv/kabel1>,prime_Kabel_000_btime,<{rgUnfold($DEVICE,'prime_Kabel_000_')}@prime_Kabel_000_title>,<|>,prime_Kabel_001_btime,<{rgUnfold($DEVICE,'prime_Kabel_001_')}@prime_Kabel_001_title>,<|>,prime_Kabel_002_btime,<{rgUnfold($DEVICE,'prime_Kabel_002_')}@prime_Kabel_002_title>\
dmy_TV:<%tv/kabel1classic>,prime_KabelEinsClassic_000_btime,<{rgUnfold($DEVICE,'prime_KabelEinsClassic_000_')}@prime_KabelEinsClassic_000_title>,<|>,prime_KabelEinsClassic_001_btime,<{rgUnfold($DEVICE,'prime_KabelEinsClassic_001_')}@prime_KabelEinsClassic_001_title>,<|>,prime_KabelEinsClassic_002_btime,<{rgUnfold($DEVICE,'prime_KabelEinsClassic_002_')}@prime_KabelEinsClassic_002_title>\
dmy_TV:<%tv/13thstreet>,prime_13thStreet_000_btime,<{rgUnfold($DEVICE,'prime_13thStreet_000_')}@prime_13thStreet_000_title>,<|>,prime_13thStreet_001_btime,<{rgUnfold($DEVICE,'prime_13thStreet_001_')}@prime_13thStreet_001_title>,<|>,prime_13thStreet_002_btime,<{rgUnfold($DEVICE,'prime_13thStreet_002_')}@prime_13thStreet_002_title>\
dmy_TV:<%tv/silverline>,prime_Silverline_000_btime,<{rgUnfold($DEVICE,'prime_Silverline_000_')}@prime_Silverline_000_title>,<|>,prime_Silverline_001_btime,<{rgUnfold($DEVICE,'prime_Silverline_001_')}@prime_Silverline_001_title>,<|>,prime_Silverline_002_btime,<{rgUnfold($DEVICE,'prime_Silverline_002_')}@prime_Silverline_002_title>\
dmy_TV:<%tv/tntfilm>,prime_TNTFilm_000_btime,<{rgUnfold($DEVICE,'prime_TNTFilm_000_')}@prime_TNTFilm_000_title>,<|>,prime_TNTFilm_001_btime,<{rgUnfold($DEVICE,'prime_TNTFilm_001_')}@prime_TNTFilm_001_title>,<|>,prime_TNTFilm_002_btime,<{rgUnfold($DEVICE,'prime_TNTFilm_002_')}@prime_TNTFilm_002_title>\
dmy_TV:<%tv/axn>,prime_AXN_000_btime,<{rgUnfold($DEVICE,'prime_AXN_000_')}@prime_AXN_000_title>,<|>,prime_AXN_001_btime,<{rgUnfold($DEVICE,'prime_AXN_001_')}@prime_AXN_001_title>,<|>,prime_AXN_002_btime,<{rgUnfold($DEVICE,'prime_AXN_002_')}@prime_AXN_002_title>\
dmy_TV:<%tv/sonytv>,prime_SonyEntertainmentTV_000_btime,<{rgUnfold($DEVICE,'prime_SonyEntertainmentTV_000_')}@prime_SonyEntertainmentTV_000_title>,<|>,prime_SonyEntertainmentTV_001_btime,<{rgUnfold($DEVICE,'prime_SonyEntertainmentTV_001_')}@prime_SonyEntertainmentTV_001_title>,<|>,prime_SonyEntertainmentTV_002_btime,<{rgUnfold($DEVICE,'prime_SonyEntertainmentTV_002_')}@prime_SonyEntertainmentTV_002_title>\
dmy_TV:<%tv/kinowelt>,prime_Kinowelt_000_btime,<{rgUnfold($DEVICE,'prime_Kinowelt_000_')}@prime_Kinowelt_000_title>,<|>,prime_Kinowelt_001_btime,<{rgUnfold($DEVICE,'prime_Kinowelt_001_')}@prime_Kinowelt_001_title>,<|>,prime_Kinowelt_002_btime,<{rgUnfold($DEVICE,'prime_Kinowelt_002_')}@prime_Kinowelt_002_title>\
dmy_TV:<%tv/pro7maxx>,prime_ProSiebenMaxx_000_btime,<{rgUnfold($DEVICE,'prime_ProSiebenMaxx_000_')}@prime_ProSiebenMaxx_000_title>,<|>,prime_ProSiebenMaxx_001_btime,<{rgUnfold($DEVICE,'prime_ProSiebenMaxx_001_')}@prime_ProSiebenMaxx_001_title>,<|>,prime_ProSiebenMaxx_002_btime,<{rgUnfold($DEVICE,'prime_ProSiebenMaxx_002_')}@prime_ProSiebenMaxx_002_title>\
dmy_TV:<%tv/sixx>,prime_Sixx_000_btime,<{rgUnfold($DEVICE,'prime_Sixx_000_')}@prime_Sixx_000_title>,<|>,prime_Sixx_001_btime,<{rgUnfold($DEVICE,'prime_Sixx_001_')}@prime_Sixx_001_title>,<|>,prime_Sixx_002_btime,<{rgUnfold($DEVICE,'prime_Sixx_002_')}@prime_Sixx_002_title>\
dmy_TV:<%tv/tntserie>,prime_TNTSerie_000_btime,<{rgUnfold($DEVICE,'prime_TNTSerie_000_')}@prime_TNTSerie_000_title>,<|>,prime_TNTSerie_001_btime,<{rgUnfold($DEVICE,'prime_TNTSerie_001_')}@prime_TNTSerie_001_title>,<|>,prime_TNTSerie_002_btime,<{rgUnfold($DEVICE,'prime_TNTSerie_002_')}@prime_TNTSerie_002_title>\
dmy_TV:<%tv/syfy>,prime_SciFi_000_btime,<{rgUnfold($DEVICE,'prime_SciFi_000_')}@prime_SciFi_000_title>,<|>,prime_SciFi_001_btime,<{rgUnfold($DEVICE,'prime_SciFi_001_')}@prime_SciFi_001_title>,<|>,prime_SciFi_002_btime,<{rgUnfold($DEVICE,'prime_SciFi_002_')}@prime_SciFi_002_title>\
dmy_TV:<%tv/ntv>,prime_ntv_000_btime,<{rgUnfold($DEVICE,'prime_ntv_000_')}@prime_ntv_000_title>,<|>,prime_ntv_001_btime,<{rgUnfold($DEVICE,'prime_ntv_001_')}@prime_ntv_001_title>,<|>,prime_ntv_002_btime,<{rgUnfold($DEVICE,'prime_ntv_002_')}@prime_ntv_002_title>\
dmy_TV:<%tv/n24>,prime_N24Doku_000_btime,<{rgUnfold($DEVICE,'prime_N24Doku_000_')}@prime_N24Doku_000_title>,<|>,prime_N24Doku_001_btime,<{rgUnfold($DEVICE,'prime_N24Doku_001_')}@prime_N24Doku_001_title>,<|>,prime_N24Doku_002_btime,<{rgUnfold($DEVICE,'prime_N24Doku_002_')}@prime_N24Doku_002_title>\
dmy_TV:<%tv/history>,prime_History_000_btime,<{rgUnfold($DEVICE,'prime_History_000_')}@prime_History_000_title>,<|>,prime_History_001_btime,<{rgUnfold($DEVICE,'prime_History_001_')}@prime_History_001_title>,<|>,prime_History_002_btime,<{rgUnfold($DEVICE,'prime_History_002_')}@prime_History_002_title>\
dmy_TV:<%tv/planet>,prime_PLANET_000_btime,<{rgUnfold($DEVICE,'prime_PLANET_000_')}@prime_PLANET_000_title>,<|>,prime_PLANET_001_btime,<{rgUnfold($DEVICE,'prime_PLANET_001_')}@prime_PLANET_001_title>,<|>,prime_PLANET_002_btime,<{rgUnfold($DEVICE,'prime_PLANET_002_')}@prime_PLANET_002_title>\
dmy_TV:<%tv/kabel1doku>,prime_KabelEinsDoku_000_btime,<{rgUnfold($DEVICE,'prime_KabelEinsDoku_000_')}@prime_KabelEinsDoku_000_title>,<|>,prime_KabelEinsDoku_001_btime,<{rgUnfold($DEVICE,'prime_KabelEinsDoku_001_')}@prime_KabelEinsDoku_001_title>,<|>,prime_KabelEinsDoku_002_btime,<{rgUnfold($DEVICE,'prime_KabelEinsDoku_002_')}@prime_KabelEinsDoku_002_title>\
dmy_TV:<%tv/animalplanet>,prime_AnimalPlanet_000_btime,<{rgUnfold($DEVICE,'prime_AnimalPlanet_000_')}@prime_AnimalPlanet_000_title>,<|>,prime_AnimalPlanet_001_btime,<{rgUnfold($DEVICE,'prime_AnimalPlanet_001_')}@prime_AnimalPlanet_001_title>,<|>,prime_AnimalPlanet_002_btime,<{rgUnfold($DEVICE,'prime_AnimalPlanet_002_')}@prime_AnimalPlanet_002_title>\
dmy_TV:<%tv/natgeo>,prime_NatGeoHD_000_btime,<{rgUnfold($DEVICE,'prime_NatGeoHD_000_')}@prime_NatGeoHD_000_title>,<|>,prime_NatGeoHD_001_btime,<{rgUnfold($DEVICE,'prime_NatGeoHD_001_')}@prime_NatGeoHD_001_title>,<|>,prime_NatGeoHD_002_btime,<{rgUnfold($DEVICE,'prime_NatGeoHD_002_')}@prime_NatGeoHD_002_title>\
dmy_TV:<%tv/tlc>,prime_TLC_000_btime,<{rgUnfold($DEVICE,'prime_TLC_000_')}@prime_TLC_000_title>,<|>,prime_TLC_001_btime,<{rgUnfold($DEVICE,'prime_TLC_001_')}@prime_TLC_001_title>,<|>,prime_TLC_002_btime,<{rgUnfold($DEVICE,'prime_TLC_002_')}@prime_TLC_002_title>\
dmy_TV:<%tv/ae>,prime_AandE_000_btime,<{rgUnfold($DEVICE,'prime_AandE_000_')}@prime_AandE_000_title>,<|>,prime_AandE_001_btime,<{rgUnfold($DEVICE,'prime_AandE_001_')}@prime_AandE_001_title>,<|>,prime_AandE_002_btime,<{rgUnfold($DEVICE,'prime_AandE_002_')}@prime_AandE_002_title>
attr rg_TV_PRIME alias TV-Programm Primetime
attr rg_TV_PRIME cellStyle { \
  'r:1,c:1' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:2' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:3' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:5' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:6' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:8' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"',\
  'r:1,c:9' => 'style="color:yellow;;text-align:center;;font-weight:bold;;"'\
}
attr rg_TV_PRIME group TV Programm
attr rg_TV_PRIME nonames 1
attr rg_TV_PRIME style style="font-size:16px;;"

Die Icons der readingsGroups müsst ihr natürlich anpassen! Ladet euch einfach irgendwo die Senderlogos runter und speichert diese unter /opt/fhem/www/images/default/tv. Vergesst bitte nicht die entsprechenden Rechte zu setzen:

sudo mkdir /opt/fhem/www/images/default/tv
sudo chown fhem:dialout /opt/fhem/www/images/default/tv

Jetzt die Icons in das Verzeichnis kopieren und die entsprechenden Rechte für diese Dateien vergeben.

sudo chown fhem:dialout *.png

Falls die Icons nicht dargestellt werden sollten, könnte eventuell der folgende Befehl helfen:

set WEB rereadicons

Variante 3 (EPG Daten selbst erstellen):

Vorbereitungen:

Mono und screen installieren:

cd ~
sudo apt-get install mono-runtime libmono-system-data4.0-cil libmono-system-web4.0-cil screen

WebGrab++ downloaden:

wget http://www.webgrabplus.com/sites/default/files/download/SW/V2.1.0/WebGrabPlus_V2.1_install.tar.gz

WebGrab++ entpacken:

tar -zxvf WebGrabPlus_V2.1_install.tar.gz

WebGrab++ installieren:

cd .wg++
./install.sh

Nun müssen zuerst einmal die Konfigurationsdateien angepasst werden:

sudo nano WebGrab++.config.xml

Diese Settings können z.B. verwendet werden:

  <filename>guide.xml</filename>
  <mode>n</mode>
  <postprocess grab="y" run="y">rex</postprocess>
  <user-agent>Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0</user-agent>
  <logging>on</logging>
  <retry time-out="15">5</retry>
  <timespan>6</timespan>
  <update>i</update>

Dann müssen noch die Channels eingetragen werden. Diese Channels können von der Webseite von WebGrab++ kopiert werden. Hier einmal Beispielhaft eine Zeile:

    <channel update="i" site="tvmovie.de" site_id="ard" xmltv_id="ARD">ARD</channel>

Jetzt die Datei speichern und schliessen.

Jetzt muss noch die Datei für die Nachbearbeitung angepasst werden:

cd rex
sudo nano rex.config.xml

Hier können diese Settings verwendet werden:

  <title lang="de">'title'</title>
  <sub-title lang="de">{Episode: 'episode' }'subtitle'</sub-title>
  <desc lang="de">'description'{\nProduced in: 'productiondate'. }{\nCategory: 'category(, )'. }{\nActors: 'actor(, )'}{\nDirector: 'director(, )'}{\nPresenter: 'presenter(, )'}</desc>
  <credits></credits>
  <episode-num></episode-num>
  <date></date>
  <category></category>
  <review>{Ratings: 'rating(, )'.}</review>
  <rating></rating>

Datei wieder abspeichern und schliessen.

EPG Daten grabben und aufbereiten:

Bitte vorsichtig sein, das grabben und aufbereiten der Daten kann extrem lange dauern. Fangt am besten mit einem oder 2 Sendern an, um einen ersten Eindruck zu bekommen.

cd ~/.wg++
./run.sh

WebGrab++ in Crontab einbinden:

Einfach nur das Script aufzurufen hat bei mir nicht funktioniert. Ich habe deshalb screen verwenden müssen.

30 0 * * 1,4 /usr/bin/screen -dmS webgrab /home/<user z.B. pi>/.wg++/run.sh

Damit wird der Vorgang alle 3-4 Tage gestartet und generiert das Programm der nächsten Woche. Die entstandene Datei könnte dann über Variante 2 weiter verarbeitet werden.

Variante 4 (RSS Daten einlesen):

Die Website texxas.de stellt das aktuelle TV-Programm in einem RSS-Feed bereit, dies erzeugt eine sehr geringe Downloadmenge. Die Website unterteilt das Programm in verschiedene Kategorien:

  • Hauptsender (ARD, ZDF, RTL, ProSieben, Sat.1, Kabel1, VOX)
  • Spartensender (Anixe HD, ARTE, Bibel TV, Comedy Central, DMAX, HGTV, Kabel Eins Classics, MTV, One)
  • Regionalsender
  • Dokumentation

rssFeed Device anlegen:

Das Attribut rfAllReadingsEvents sorgt dafür, dass EPG-Aktualisierung auch zu Events führen in Verbindung mit event-on-change / event-on-update. Siehe dazu commandref zum Modul rssFeed.

defmod Dev_Multimedia_EPG_Spartensender rssFeed http://www.texxas.de/tv/spartensenderJetzt.xml 300
attr Dev_Multimedia_EPG_Spartensender rfAllReadingsEvents 1

Anschließend liegen je Sender zwei Readings vor:

Reading Wert
n01_title ARD Alpha: Panoramabilder
n01_description 20.11.2022 08:20 - 09:45
n02_title ARTE: 27 - Das europäische Magazin
n02_description 20.11.2022 09:30 - 10:15<br>In Europa ist die Sterbehilfe in gerade einmal vier Ländern erlaubt: in den Niederlanden, Belgien, Luxemburg und seit neuestem auch in Spanien. Die Schweiz hat mit der aktiven Sterbehilfe einen alternativen Weg gewählt. In Frankreich hat die nationale Ethikkommission das Thema im September wieder auf den Tisch gebracht und sich für eine "streng geregelte" Form der Sterbehilfe ausgesprochen. Wie sollten kranke Menschen mit Sterbewillen begleitet werden? Unter welchen Umständen ist die akt ...
Beispiel