TV Programm

Aus FHEMWiki
Wechseln zu: Navigation, Suche

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)/;
my $timeAdjust = 0;
#my $channelFilter = qr/^(?:ARD|ZDF|SAT\.1|RTL$|RTL II|PRO 7|DMAX|VOX|KABEL 1)/;
#my $timeAdjust = 86400;


my $redt = qr/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(?:\s+([+-]\d{4}))?$/;


sub xmltv2epoch($)
{
  my $dt = shift;
  
  if ($dt =~ $redt)
  {
    if (defined($7))
    {
      return $1.'-'.$2.'-'.$3.' '.$4.':'.$5.':'.$6.' '.$7;
    }
    else
    {
      return $1.'-'.$2.'-'.$3.' '.$4.':'.$5.':'.$6;
    }
  }
  
  return '2000-01-01 00:00:00';
}

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 = shift;
  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 $primeTime = substr(FmtDateTime(time() - $timeAdjust), 0, 11).'20:14:00';
  my $sendTelnet = '';

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

        # filter old stuff
        if (($stop + $timeAdjust) >= time())
        {
          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)
          {
            my $fi = sprintf("%03d", $i);
            my $start = str2time(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";
            
            #my $result = `perl /opt/fhem/fhem.pl 7072 "$sendTelnet"`;
           
            $i++;
          }
          
          if ($n < 3)
          {
            my $start = str2time(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";
              
              #print $readingValue."\n";
              
              $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";
              
              #my $result = `perl /opt/fhem/fhem.pl 7072 "$sendTelnet"`;
                            
              $n++;
            }
          }
        }
      }
    }

    system('/opt/fhem/fhem.pl 7072 "'.$sendTelnet.'"');
  }
}

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);
  #print $output;
  $output = qx(xz -df /opt/fhem/tv/rytecDE_Basic.xz 2>&1);
  #print $output;
}


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

if ('download' eq $m)
{
  tvDownload();
}
elsif ('parse' eq $m)
{
  tvParse($d);
}

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 download

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:

2 "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). Die 2 "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:15:00 {if ((1 == $wday) || (4 == $wday)) {fhem("perl /opt/fhem/tv/tv.pl dmy_TV download")}}
defmod at_TV_UPDATE at +*00:15:00 "perl /opt/fhem/tv/tv.pl dmy_TV parse"

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>
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>\

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):

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.

httpmod Device anlegen:

Vergesst bitte nicht im unten stehenden Code euren API Key einzutragen!

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
attr TV_JETZT_HAUPTSENDER userattr get01Name get01URL getEncode readingEncode
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

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.

Beispiel