TV Programm

Aus FHEMWiki

Sich das aktuelle Fernsehprogramm in FHEM anzeigen zu lassen, ist leider gar nicht so einfach. Die einzelnen Anbieter stellen leider 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 (enthaä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.
  • Ü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:

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/(.{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>"; } sub xmltv2epoch($) { my $dt = shift; if ($dt =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(?:\s+([+-]\d{4}))?$/) { 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 tvParse($) { use utf8; use Date::Parse; use Encode qw(encode_utf8 decode_utf8); use XML::Bare qw(forcearray); my $device = shift; my $hash = $defs{$device}; my $obj = XML::Bare->new(file => '/opt/fhem/tv/rytecDE_Basic'); my $xml = $obj->simple(); my $start; my $stop; my $i = 0; my $fi = '000'; my $lastChannel = ''; my $reading = ''; if (!$@) { # clear all old internals delete($hash->{helper}); readingsBeginUpdate($hash); foreach (@{forcearray($xml->{tv}{programme})}) { # channel filter if ($_->{'channel'} =~ /^(?:ARD\.|ZDF\.|Sat1\.|RTL\.|RTL2\.|Pro7\.|DMax\.|Vox\.|Kabel\.)/) { $stop = str2time(xmltv2epoch($_->{'stop'})); # filter old stuff if ($stop >= time()) { if ($lastChannel ne $_->{'channel'}) { $lastChannel = $_->{'channel'}; $reading = $_->{'channel'}; $reading =~ s/\..*$//; $i = 0; $hash->{helper}{$reading.'_lastIndex'} = 0; } # limit number of readings next if ($i > 75); $fi = sprintf("%03d", $i); $start = str2time(xmltv2epoch($_->{'start'})); $hash->{helper}{$reading.'_'.$fi.'_bdate'} = substr(FmtDateTime($start), 0, 10); $hash->{helper}{$reading.'_'.$fi.'_btime'} = substr(FmtDateTime($start), 11, 8); $hash->{helper}{$reading.'_'.$fi.'_edate'} = substr(FmtDateTime($stop), 0, 10); $hash->{helper}{$reading.'_'.$fi.'_etime'} = substr(FmtDateTime($stop), 11, 8); $hash->{helper}{$reading.'_'.$fi.'_title'} = encode_utf8($_->{'title'}{'content'}); if (exists($_->{'sub-title'}{'content'})) { $hash->{helper}{$reading.'_'.$fi.'_stitle'} = encode_utf8($_->{'sub-title'}{'content'}); } else { $hash->{helper}{$reading.'_'.$fi.'_stitle'} = 'na'; } if (exists($_->{'desc'}{'content'})) { $hash->{helper}{$reading.'_'.$fi.'_desc'} = encode_utf8($_->{'desc'}{'content'}); } else { $hash->{helper}{$reading.'_'.$fi.'_desc'} = 'na'; } if ($i < 3) { readingsBulkUpdate($hash, 'next_'.$reading.'_'.$fi.'_bdate', substr(FmtDateTime($start), 0, 10)); readingsBulkUpdate($hash, 'next_'.$reading.'_'.$fi.'_btime', substr(FmtDateTime($start), 11, 8)); readingsBulkUpdate($hash, 'next_'.$reading.'_'.$fi.'_edate', substr(FmtDateTime($stop), 0, 10)); readingsBulkUpdate($hash, 'next_'.$reading.'_'.$fi.'_etime', substr(FmtDateTime($stop), 11, 8)); readingsBulkUpdate($hash, 'next_'.$reading.'_'.$fi.'_title', encode_utf8($_->{'title'}{'content'})); if (exists($_->{'sub-title'}{'content'})) { readingsBulkUpdate($hash, 'next_'.$reading.'_'.$fi.'_stitle', encode_utf8($_->{'sub-title'}{'content'})); } else { readingsBulkUpdate($hash, 'next_'.$reading.'_'.$fi.'_stitle', 'na'); } if (exists($_->{'desc'}{'content'})) { readingsBulkUpdate($hash, 'next_'.$reading.'_'.$fi.'_desc', encode_utf8($_->{'desc'}{'content'})); } else { readingsBulkUpdate($hash, 'next_'.$reading.'_'.$fi.'_desc', 'na'); } } $i++; } } } readingsBulkUpdate($hash, 'state', 'parsed'); readingsEndUpdate($hash, 0); } return undef; } sub tvDownload() { my $output = qx(wget http://rytecepg.ipservers.eu/epg_data/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; } sub tvUpdate($) { my $device = shift; my $hash = $defs{$device}; my @channels = ( 'ARD', 'ZDF', 'Sat1', 'RTL', 'RTL2', 'Pro7', 'DMax', 'Vox', 'Kabel' ); if (exists($hash->{helper})) { readingsBeginUpdate($hash); foreach my $channel (@channels) { my $lastIndex = (exists($hash->{helper}{$channel.'_lastIndex'}) ? $hash->{helper}{$channel.'_lastIndex'} : undef); my $newLastIndex = $lastIndex; my $isNew = 1; if (defined($lastIndex)) { my $i = 0; my $edate = (exists($hash->{helper}{$channel.'_'.sprintf("%03d", $lastIndex).'_edate'}) ? $hash->{helper}{$channel.'_'.sprintf("%03d", $lastIndex).'_edate'} : undef); while (($i < 3) && (defined($edate))) { my $index = sprintf("%03d", $lastIndex); my $etime = (exists($hash->{helper}{$channel.'_'.$index.'_etime'}) ? $hash->{helper}{$channel.'_'.$index.'_etime'} : undef); if ($edate.' '.$etime gt TimeNow()) { my $nindex = sprintf("%03d", $i); if ($lastIndex == $newLastIndex) { $edate = undef; last; } if (1 == $isNew) { $hash->{helper}{$channel.'_lastIndex'} = $lastIndex; $isNew = 0; } readingsBulkUpdate($hash, 'next_'.$channel.'_'.$nindex.'_bdate', $hash->{helper}{$channel.'_'.$index.'_bdate'}); readingsBulkUpdate($hash, 'next_'.$channel.'_'.$nindex.'_btime', $hash->{helper}{$channel.'_'.$index.'_btime'}); readingsBulkUpdate($hash, 'next_'.$channel.'_'.$nindex.'_edate', $edate); readingsBulkUpdate($hash, 'next_'.$channel.'_'.$nindex.'_etime', $etime); readingsBulkUpdate($hash, 'next_'.$channel.'_'.$nindex.'_title', $hash->{helper}{$channel.'_'.$index.'_title'}); readingsBulkUpdate($hash, 'next_'.$channel.'_'.$nindex.'_stitle', $hash->{helper}{$channel.'_'.$index.'_stitle'}); readingsBulkUpdate($hash, 'next_'.$channel.'_'.$nindex.'_desc', $hash->{helper}{$channel.'_'.$index.'_desc'}); $i++; } $lastIndex++; $edate = (exists($hash->{helper}{$channel.'_'.sprintf("%03d", $lastIndex).'_edate'}) ? $hash->{helper}{$channel.'_'.sprintf("%03d", $lastIndex).'_edate'} : undef); } } } readingsBulkUpdate($hash, 'state', 'updated'); readingsEndUpdate($hash, 1); } else { tvParse($device); } } sub tvUpdatePrimetime($) { my $device = shift; my $hash = $defs{$device}; my @channels = ( 'ARD', 'ZDF', 'Sat1', 'RTL', 'RTL2', 'Pro7', 'DMax', 'Vox', 'Kabel' ); if (exists($hash->{helper})) { readingsBeginUpdate($hash); foreach my $channel (@channels) { my $lastIndex = (exists($hash->{helper}{$channel.'_lastIndex'}) ? $hash->{helper}{$channel.'_lastIndex'} : undef); my $newLastIndex = $lastIndex; if (defined($lastIndex)) { my $i = 0; my $bdate = (exists($hash->{helper}{$channel.'_'.sprintf("%03d", $lastIndex).'_bdate'}) ? $hash->{helper}{$channel.'_'.sprintf("%03d", $lastIndex).'_bdate'} : undef); while (($i < 3) && (defined($bdate))) { my $index = sprintf("%03d", $lastIndex); my $btime = (exists($hash->{helper}{$channel.'_'.$index.'_btime'}) ? $hash->{helper}{$channel.'_'.$index.'_btime'} : undef); my $timeNow = substr(TimeNow(), 0, 11).'20:14:00'; if ($bdate.' '.$btime gt $timeNow) { my $nindex = sprintf("%03d", $i); if ($lastIndex == $newLastIndex) { $bdate = undef; last; } readingsBulkUpdate($hash, 'prime_'.$channel.'_'.$nindex.'_bdate', $bdate); readingsBulkUpdate($hash, 'prime_'.$channel.'_'.$nindex.'_btime', $btime); readingsBulkUpdate($hash, 'prime_'.$channel.'_'.$nindex.'_edate', $hash->{helper}{$channel.'_'.$index.'_edate'}); readingsBulkUpdate($hash, 'prime_'.$channel.'_'.$nindex.'_etime', $hash->{helper}{$channel.'_'.$index.'_etime'}); readingsBulkUpdate($hash, 'prime_'.$channel.'_'.$nindex.'_title', $hash->{helper}{$channel.'_'.$index.'_title'}); readingsBulkUpdate($hash, 'prime_'.$channel.'_'.$nindex.'_stitle', $hash->{helper}{$channel.'_'.$index.'_stitle'}); readingsBulkUpdate($hash, 'prime_'.$channel.'_'.$nindex.'_desc', $hash->{helper}{$channel.'_'.$index.'_desc'}); $i++; } $lastIndex++; $bdate = (exists($hash->{helper}{$channel.'_'.sprintf("%03d", $lastIndex).'_bdate'}) ? $hash->{helper}{$channel.'_'.sprintf("%03d", $lastIndex).'_bdate'} : undef); } } } readingsBulkUpdate($hash, 'state', 'updated'); readingsEndUpdate($hash, 1); } }