Fernsehprogramm DOIF (aktuell und primetime | non blocking | minimaler traffic)

Begonnen von mumpitzstuff, 12 Juni 2020, 21:47:48

Vorheriges Thema - Nächstes Thema

Per

Ich habe inzwischen 90%, naja, sagen wir 80% von deinem Code verstanden. Hier noch mal meine aktuelle Variante zur Diskussion (inkl. deiner Änderungen vom letzten Post).
Neu:
- "rytecDE_leer" wird nicht mehr benötigt
- Änderungen an den TPL erzeugen nicht mehr automatisch einen neuen Durchlauf, sondern werden über "set TV_Programm _refill" manuell angestoßen. Die automatische Variante ergab mir zuviele "warnings"
- Datum und Zeit wurde zu einem Reading zusammengefasst (-20% Readings ;))
- Ein TPL wurde für Mediathek-Links erstellt.
- Download, Merge und Parse sind separate Prozesse
- Merge wird nicht mehr mehrfach aufgerufen (((Start+Ziel)+Ziel)+Ziel), sondern in einer foreach-Schleife
- Das Ende von Merge ruft automatisch Parse auf, daher keine feste Verzögerung mehr nötig
- "rytecDE_SportMovies" muss nur noch an einer Stelle aktiviert (#) werden

defmod TV_Programm DOIF subs\
{\
  \
  use utf8;;\
  use Date::Parse;;\
  # sudo apt-get install libxml-bare-perl\
  use XML::Bare 0.53 qw(forcearray);;\
  use Blocking;;\
  $_name = "$SELF";;\
  $_path = qx(pwd 2>&1);;\
  $_path =~ s/\/?\s/\//;;\
  #'/opt/fhem/';;\
  $_data = '/rytecDE_filt_'.$_name;;\
  $_dataFile = $_path.$_data;;\
  #use Encode qw(encode_utf8 decode_utf8);;\
  \
  ### CONFIG AREA ###\
  $_server = AttrVal($_name,"server","http://epg.vuplus-community.net");; \
  my $channelFilter = ReadingsVal($_name,"sender","ZDF\\.|");;\
  $channelFilter =~ s/\|$//;;\
  $_channelFilter = qr/^(?:$channelFilter)/;;\
  # telnet port must not be password protected (open)\
  # this is used as fallback if telnet port cannot created automatically\
  $_telnetPort = 7072;;\
  # can be used to adjust the program times (mostly not needed!)\
  $_timeAdjust = 0;;\
  # enable/disable unused channel filtering on filemerge (enabled = small file = faster)\
  $_filterChannels = 1;;\
  # enable/disable updates based on starttimes (enabled = update channels only if needed = faster)\
  $_updateBasedOnStarttimes = 1;;\
  # enable/disable use of Time::Piece (timepiece is faster but not instaled on some systems)\
  $_timepiece = 0;;\
  \
  # internal variables\
  $_startTimes = ();;\
\
  sub filterText($)\
  {\
    my $text = shift;;\
    \
    $text =~ s/[\x{0022}\x{0060}\x{003b}\x{0027}\"\`;;\'\r]//g;;\
    #$text =~ s/[\"\`;;\'\r]//g;;\
    $text =~ s/[\n]/<br>/g;;\
    \
    return $text;;\
  }\
\
  sub xmltv2epoch($)\
  {\
    my $t = shift;;\
    \
    if ($_timepiece)\
    {\
      use Time::Piece;;\
      \
      # fast version\
      return Time::Piece->strptime($t, '%Y%m%d%H%M%S %z')->epoch;;\
    }\
    else\
    {\
      # slow but compatible version\
      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 createTelnet($)\
  {\
    my $device = shift;;\
    my $telnet = undef;;\
    \
    foreach my $d (sort keys %::defs)\
    {\
      next if ($d !~ /telnetForTvUpdateFn_\d+/);;\
      my $h = $::defs{$d};;\
      next if (!$h->{TYPE} || $h->{TYPE} ne 'telnet' || $h->{SNAME});;\
      next if (::AttrVal($d, 'allowfrom', '127.0.0.1') ne '127.0.0.1');;\
      next if ($h->{DEF} !~ /^\d+( global)?$/);;\
      next if ($h->{DEF} =~ /IPV6/);;\
      \
      $telnet = $d;;\
      last;;\
    }\
    \
    if (!defined($telnet))\
    {\
      $telnet = 'telnetForTvUpdateFn_'.time();;\
      my $ret = ::CommandDefine(undef, "-temporary $telnet telnet 0");;\
      \
      if (defined($ret))\
      {\
        Log3 $device, 1, $device.': Cannot create telnet port ('.$ret.')';;\
        return undef;;\
      }\
      \
      $::attr{$telnet}{room} = 'hidden';;\
      $::attr{$telnet}{allowfrom} = '127.0.0.1';;\
    }\
    \
    return $::defs{$telnet}{PORT};;\
  }\
\
  sub sendTelnet($$)\
  {\
    my ($name, $sendTelnet) = @_;;\
    my $port;;\
    $port = createTelnet($name);;\
    $port = $_telnetPort if (!defined($port));;\
    `perl /opt/fhem/fhem.pl $port "$sendTelnet"`;;\
  }\
\
  sub DOIF::doDownload($)\
  {\
    my ($name, $data) = split("\\|", shift);;\
    my $output = '';;\
    $output .= qx(wget $_server/$data.xz -O $_path/$data.xz 2>&1);;\
    $output .= qx(xz -df $_path/$data.xz 2>&1);;\
    \
    return $name.'|'. $data.'|'.$output;;\
  }\
\
  sub DOIF::endDownload($)\
  {\
    my ($name, $data, $output) = split("\\|", shift);;\
    \
    Log3 $name, 5, $name.': '.$output;;\
    Log3 $name, 4, $name.': Blocking call finished to download tv data.';;\
    \
    my $blockname = "PID_DOWNLOAD".$data;;\
    delete($_blockingcalls{$blockname});;\
    $_lastdown = time();;\
    sendTelnet($name,";;setreading $name $data $_lastdown;;");;\
  }\
\
  sub DOIF::abortDownload($)\
  {\
    my ($name, $data) = split("\\|", shift);;\
    \
    my $blockname = "PID_DOWNLOAD".$data;;\
    delete($_blockingcalls{$blockname});;\
    Log3 $name, 1, $name.': Blocking call aborted (download).';;\
    $_lastdown = 0;;\
    sendTelnet($name,";;setreading $name $data $_lastdown;;");;\
  }\
\
  sub startDownload($)\
  {\
    my $fileName = shift;;\
    my $dataFile = $_path.$fileName;;\
    \
    # prevent download spamming\
    if (-e $dataFile)\
    {\
      my $btime = ((time() - (stat($dataFile))[9]) / 60.0 / 60.0 / 24.0);;\
      \
      if ($btime < 1.0)\
      {\
        Log3 $_name, 1, $_name.': Download of '.$fileName.' skipped because file is not older than 1 day ('.($btime).').';;\
        return;;\
      }\
    }\
    \
    my $blockname = "PID_DOWNLOAD".$fileName;;\
    if (defined($_blockingcalls{$blockname}))\
    {\
      Log3 $_name, 3, $_name.': Blocking call already running (download).';;\
      ::BlockingKill($_blockingcalls{$blockname});;\
    }\
    \
    $_blockingcalls{$blockname} = ::BlockingCall('DOIF::doDownload', $_name.'|'.$fileName, 'DOIF::endDownload', 300, 'DOIF::abortDownload', $_name.'|'.$fileName);;\
  }\
\
  sub DOIF::doMerge($)\
  {\
    my ($name, $data) = split("\\|", shift);;\
    my $output = '';;\
    \
    my @srcName = ('rytecDE_Basic','rytecDE_Common','rytecDE_SportMovies');;\
    my $fh;;\
    my $dst;;\
    my $src;;\
    my $start = '';;\
    my $channels = '';;\
    my $programms = '';;\
    my $end = '';;\
    my $pos;;\
    my $output = "";;\
    my $ftime = 2;;\
    \
    open($fh, '<', $_path.$srcName[0]) or die "Can't open file $!";;\
    read($fh, $dst, -s $fh);;\
    close($fh);;\
    \
    if (-1 != ($pos = index($dst, '<channel ')))\
    {\
      $start = substr($dst, 0, $pos);;\
    }\
    \
    if (-1 != ($pos = rindex($dst, '</programme>')))\
    {\
      $end = substr($dst, $pos + 12);;\
    }\
    \
    if (0 != $_filterChannels)\
    {\
      my $FilesCount = (@srcName);;\
      for ($i=0;;$i<$FilesCount;;$i++)\
      {\
$src = $srcName[$i];;\
if (-e $src)\
{\
          open($fh, '<', $src) or die "Can't open file $!";;\
          read($fh, $src, -s $fh);;\
          close($fh);;\
          while ($src =~ /(\s*<channel\s.*?id="(.*?)".*?<\/channel>)/sg)\
          {\
            $_ = $1;;\
            \
            if ($2 =~ $_channelFilter)\
            {\
              $channels .= $_;;\
            }\
          }\
          \
          while ($src =~ /(\s*<programme\s.*?channel="(.*?)".*?<\/programme>)/sg)\
          {\
            $_ = $1;;\
            \
            if ($2 =~ $_channelFilter)\
            {\
              $programms .= $_;;\
            }\
          }\
        }\
      }\
    }\
    open($fh, '>', $_dataFile) or die "Can't open file $!";;\
    print $fh $start.$channels.$programms.$end;;\
    close($fh);;\
    \
    return $name.'|'.$output;;\
  }\
\
  sub DOIF::endMerge($)\
  {\
    my ($name, $output) = split("\\|", shift);;\
    \
    Log3 $name, 5, $name.': '.$output;;\
    Log3 $name, 4, $name.': Blocking call finished to download tv data.';;\
    \
    delete($_blockingcalls{PID_MERGE});;\
    $_lastmerge = time();;\
    sendTelnet($name,";;setreading $name lastmerge $_lastmerge;;");;\
  }\
\
  sub DOIF::abortMerge($)\
  {\
    my $name = shift;;\
    \
    delete($_blockingcalls{PID_MERGE});;\
    \
    Log3 $name, 1, $name.': Blocking call aborted (download).';;\
    $_lastmerge = 0;;\
    sendTelnet($name,";;setreading $name lastmerge $_lastmerge;;");;\
  }\
\
  sub startMerge($)\
  {\
    my $name = shift;;\
    \
    my $channelFilter = ReadingsVal($_name,"sender","ZDF\\.|");;\
    $channelFilter =~ s/\|$//;;\
    $_channelFilter = qr/^(?:$channelFilter)/;;\
    \
    if (defined($_blockingcalls{PID_DOWNLOADrytecDE_Basic})\
    or defined($_blockingcalls{PID_DOWNLOADrytecDE_Common})\
    or defined($_blockingcalls{PID_DOWNLOADrytecDE_SportMovies}))\
    {\
      set_Exec('init_merge', 10, 'startMerge("$SELF")');;\     
      return;;\
    }\
    if (defined($_blockingcalls{PID_MERGE}))\
    {\
      Log3 $name, 3, $name.': Blocking call already running (merge).';;\
      \
      ::BlockingKill($_blockingcalls{PID_MERGE});;\
    }\
    \
    $_lastmerge = 0;;\
    sendTelnet($name,";;setreading $name lastmerge $_lastmerge;;");;\
    \
    $_blockingcalls{PID_MERGE} = ::BlockingCall('DOIF::doMerge', $name.'|'.$_data, 'DOIF::endMerge', 300, 'DOIF::abortMerge', $name);;\
  }\
\
\
  sub tvParse($$$)\
  {\
    my ($device, $mode, $port) = @_;;\
    my $obj;;\
    my $xml;;\
    my $lastChannel = '';;\
    my $reading = '';;\
    my $n = 999;;\
    my $p = 999;;\
    my $k = 0;;\
    my $primeTime = substr(FmtDateTime(time() + $_timeAdjust), 0, 11).'20:14:00';;\
    my $sendTelnet = '';;\
    my $nextparse = time() + 3600;;\
    my $start;;\
    my $stop;;\
    my $startform;;\
    my $readingValue;;\
    my $setreading;;\
    my $old = time() + $_timeAdjust;;\
    \
    ## check if any update is needed\
    if ((0 != $_updateBasedOnStarttimes) && ('prime' ne $mode) && keys(%{$_startTimes}))\
    {\
      my $nothingTodo = 1;;\
      foreach (keys(%{$_startTimes}))\
      {\
        if ($_startTimes{$_} <= $old)\
        {\
          $nothingTodo = 0;;\
          ::Log3 $device, 4, $device.': Update is not blocked because at least one actual program is finished (reading: '.$_.', start: '.$_startTimes{$_}.', old: '.$old.').';;\
          last;;\
        }\
      }\
      \
      if (0 != $nothingTodo)\
      {\
        ::Log3 $device, 4, $device.': Update is blocked because no actual program was finished.';;\
        return %{$_startTimes};;\
      }\
    }\
    \
    $obj = XML::Bare->new(file => $_dataFile);;\
    $xml = $obj->parse();;\
    \
    if (!$@)\
    {\
#      my $old = time() + $_timeAdjust;;\
      \
      foreach (@{forcearray($xml->{'tv'}{'programme'})})\
      {\
        $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$//;;\
            $p = 0;;\
    \
            $nextparse = $stop if (($nextparse > $stop) and ($stop > (time() + 30)));;\
            if ((0 == $_updateBasedOnStarttimes) || !exists($_startTimes{$reading}) || ($_startTimes{$reading} <= $old))\
            {\
              $n = 0;;\
      $_startTimes{$reading} = $stop if (0 != $_updateBasedOnStarttimes);;\
            }\
            else\
            {\
            Log3 $device, 4, $device.': '.$reading.' is blocked because actual program is not finished (reading: '.$reading.', start: '.$_startTimes{$reading}.', old: '.$old.').';;\
#              Log3 $device, 4, $device.': '.$reading.' is blocked because actual program is not finished (start: '.$_startTimes{$reading}.', old: '.$old.').';;\
            }\
          }\
  \
          $start = xmltv2epoch($_->{'start'}{'value'});;\
      $startform = substr(FmtDateTime($start), 0, 19);;\
  \
          if (($p < 3 && 'prime' eq $mode && $startform gt $primeTime) or ($n < 3 && 'next' eq $mode))\
          {\
            $readingValue = "";;\
            \
            $setreading .= ";;setreading $device _$reading";;\
    if ('prime' eq $mode)\
    {\
              $setreading .= "_prime_".sprintf("%03d", $p++);;\
    }\
    else\
    {\
              $setreading .= "_next_".sprintf("%03d", $n++);;\
    }\
    \
            $readingValue = $startform;;\
            $sendTelnet .= $setreading."_1start $readingValue";;\
    \
            $readingValue = filterText(@{forcearray($_->{'title'})}[0]->{'value'});;\
            $sendTelnet .= $setreading."_2title $readingValue";;\
    \
            $readingValue = ((exists($_->{'sub-title'}{'value'})) ? filterText($_->{'sub-title'}{'value'}) : 'na');;\
            $sendTelnet .= $setreading."_3stitle $readingValue";;\
    \
            $readingValue = ((exists($_->{'desc'}{'value'})) ? filterText($_->{'desc'}{'value'}) : 'na');;\
            $sendTelnet .= $setreading."_4desc $readingValue";;\
    \
            $k++;;\
          }\
  \
          if ($k >= 8)\
          {\
    #Log3 $device, 5, $device.': '.encode_utf8($sendTelnet);;\
            \
            `perl /opt/fhem/fhem.pl $port "$sendTelnet"`;;\
            \
            $k = 0;;\
            $sendTelnet = '';;\
          }\
        }\
      }\
      \
      if ('' ne $sendTelnet or 'next' eq $mode)\
      {\
$sendTelnet .= ";;setreading $device nextparse ". ($nextparse - time() + 10) if ('next' eq $mode);;\
         #Log3 $device, 5, $device.': '.encode_utf8($sendTelnet);;\
        \
        `perl /opt/fhem/fhem.pl $port "$sendTelnet"`;;\
      }\
    }\
    \
    return %{$_startTimes};;\
  }\
\
  sub DOIF::doParse($)\
  {\
    my ($name, $mode, $port) = split("\\|", shift);;\
    my $ret = $name;;\
    my %startTimes = tvParse($name, $mode, $port);;\
    \
    foreach (keys(%startTimes))\
    {\
      $ret .= '|'.$_.'|'.$mode.'|'.$startTimes{$_};;\
    }\
    \
    return $ret;;\
  }\
\
  sub DOIF::endParse($)\
  {\
    my ($name, $mode, @newStartTimes) = split("\\|", shift);;\
    \
    for (my $i = 0;; $i < scalar(@newStartTimes);; $i += 2)\
    {\
      $_startTimes{$newStartTimes[$i]} = $newStartTimes[$i + 1];;\
    }\
    \
    Log3 $name, 4, $name.': Blocking call finished to parse tv data.';;\
    \
    my $testparse = "PID_PARSE".$mode;;\
    delete($_blockingcalls{PID_PARSE});;\
  }\
\
  sub DOIF::abortParse($)\
  {\
    my ($name, $mode) = split("\\|", shift);;\
    \
    Log3 $name, 1, $name.': Blocking call aborted (parse).';;\
    \
    my $testparse = "PID_PARSE".$mode;;\
    delete($_blockingcalls{PID_PARSE});;\
  }\
\
  sub startParse($$)\
  {\
    my ($name, $mode) = @_;;\
    \
    if (defined($_blockingcalls{PID_MERGE}))\
    {\
      return;;\
    }\
    my $testparse = "PID_PARSE".$mode;;\
    if (defined($_blockingcalls{$testparse}))\
    {\
      Log3 $name, 3, $name.': Blocking call already running (parse).';;\
      ::BlockingKill($_blockingcalls{$testparse});;\
    }\
    \
    my $port;;\
    $port = createTelnet($name);;\
    $port = $_telnetPort if (!defined($port));;\
    \
    $_blockingcalls{$testparse} = ::BlockingCall('DOIF::doParse', $name.'|'.$mode.'|'.$port, 'DOIF::endParse', 300, 'DOIF::abortParse', $name.'|'.$mode);;\
  }\
}\
\
init\
{\
  fhem("set $SELF down");;\
  set_Exec('init_merge', 20, 'startMerge("$SELF")');;\
  set_Exec('init_next_sik', 7200, 'startParse("$SELF", "next")');;\
}\
down\
{\
  [00:05|Mo Do];;\
  set_Exec('init_down_Basic', 1, 'startDownload("rytecDE_Basic")');;\
  set_Exec('init_down_Common', 2, 'startDownload("rytecDE_Common")');;\
#  set_Exec('init_down_Sport', 3, 'startDownload("rytecDE_SportMovies")');;\
}\
merge\
{\
  [01:05|Mo Do];;\
  set_Exec('init_merge', 10, 'startMerge("$SELF")');;\
}\
parse_next_manu\
{\
  [$SELF:lastmerge];;\
  set_Exec('init_next', 5, 'startParse("$SELF", "next")');;\
}\
parse_next\
{\
  set_Exec('init_next', [$SELF:nextparse], 'startParse("$SELF", "next")');;\
  set_Exec('init_next_sik', 3601, 'startParse("$SELF", "next")');;\
}\
parse_prime\
{\
  [00:30];;\
  [$SELF:lastmerge];;\
  set_Exec('init_prime', 10, 'startParse("$SELF", "prime")');;\
}\
_refill\
{\
#  if ([$SELF:sender])\
  {\
    if (ReadingsVal("$SELF","sender","ZDF\\.") ne $_Senderliste)\
    {\
      fhem("deletereading $SELF .*(next|prime)_.*  200000");;\
      fhem("setreading $SELF sender $_Senderliste");;\
      fhem("set $SELF down");;\
      set_Exec('init_merge', 20, 'startMerge("$SELF")');;\
    }\
    fhem("setreading $SELF rowsnext $_rowsnext");;\
    fhem("setreading $SELF rowsprime $_rowsprime");;\
    \
  }\
}
attr TV_Programm userattr server:http://epg.vuplus-community.net,http://www.xmltvepg.nl,http://91.121.106.172/~rytecepg/epg_data,http://www.vuplus-community.net/rytec
attr TV_Programm alias Aktuelles TV-Programm
attr TV_Programm event-on-change-reading .*
attr TV_Programm room Wohnzimmer
attr TV_Programm server http://www.xmltvepg.nl
attr TV_Programm uiTable {\
  package ui_Table;;\
  \
  $SHOWNOSTATE = 1;;\
  $ATTRIBUTESFIRST = 1;;\
  my $rowsnext = ::ReadingsVal("$SELF","rowsnext",0);;\
  my $rowsprime = ::ReadingsVal("$SELF","rowsprime",0);;\
  my $lastrow = $rowsnext + $rowsprime + 2;;\
  \
  $TR{0} = "style='color:red;;text-align:center;;font-weight:bold;;font-size:18px;;'";;\
  $TR{($rowsnext + 2)} = "style='color:red;;text-align:center;;font-weight:bold;;font-size:18px;;'";;\
  $TR{1..($rowsnext),($rowsnext + 4)..$lastrow} = "style='font-size:16px'";;\
  $TR{($rowsnext + 1)} = "style='border-top-style:solid;;border-bottom-style:solid;;border-color:#CCCCCC;;border-top-width:1px;;border-bottom-width:1px;;'";;\
#  $TD{0..$lastrow}{0} = "align='center'";;\
  $TD{0..$rowsnext, ($rowsnext + 3)..$lastrow}{1,3,5} = "style='border-left-style:solid;;border-color:#CCCCCC;;border-left-width:1px;;'";;\
\
  sub showIcon\
  {\
    my ($mode, $icon, $show, $device, $state) = @_;;\
    $_rowsprime_temp += ($mode eq "prime" ? 1 : 0);;\
    $_rowsnext_temp += ($mode eq "next" ? 1 : 0);;\
    $_Senderliste_temp =~ s/$icon\\.\|//;;\
    $_Senderliste_temp .= $icon . "\\.|";;\
    \
    if ($device && $state)\
    {\
      return "<a href=\"". ::ReadingsVal("$SELF","$device","") =~ s/.state/$state/r ."\" target=\"IPTV\">".ICON("$show")."</a>";;\
#      return "<a href=\"$::FW_ME?cmd=set $device $state$::FW_CSRF\">".ICON("tv/$icon")."</a>";;\
    }\
    else\
    {\
      if ($device)\
      {\
        return "<a href=\"$device\" target=\"IPTV\">".ICON("tv/20px-Icon-video-library")." ".ICON("$show")."</a>";;\
      }\
      else\
      {\
        return ICON("$show");;\
      }\
    }\
  }\
\
  sub unfold\
  {\
    my ($ReadingPre) = @_;;\
    my $title = ::ReadingsVal("$SELF","_${ReadingPre}_2title","-");;\
    my $desc = ::ReadingsVal("$SELF","_${ReadingPre}_3stitle","na")."\n\n". ::ReadingsVal("$SELF","_${ReadingPre}_4desc","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 substr(::ReadingsVal("$SELF","_${ReadingPre}_1start",""),11,5) . \
            "</td><td><a href=\"#!\" onclick=\"FW_okDialog(';$title<br>$desc';)\">$title</a>";;\
  }\
  \
  sub save_vars\
  {\
    $_Senderliste = $_Senderliste_temp ;;\
    $_rowsprime = $_rowsprime_temp;;\
    $_rowsnext = $_rowsnext_temp;;\
    return;;\
  }\
}\
\
## parameter: mode (next or prime), channel name (see xml data file), icon name (filename of channel logo), device name for set command, command\
DEF TPL_TVSET(showIcon("$1", "$2", "$3", "$4", "$5")|unfold("$2_$1_000")|unfold("$2_$1_001")|unfold("$2_$1_002"))\
\
## parameter: mode (next or prime), channel name (see xml data file), icon name (filename of channel logo)\
DEF TPL_TV(showIcon("$1", "$2", "$3")|unfold("$2_$1_000")|unfold("$2_$1_001")|unfold("$2_$1_002"))\
\
## parameter: mode (next or prime), channel name (see xml data file), icon name (filename of channel logo), URL Mediathek\
DEF TPL_TVMT(showIcon("$1", "$2", "$3", "$4")|unfold("$2_$1_000")|unfold("$2_$1_001")|unfold("$2_$1_002"))\
\
DEF TPL_END(save_vars())\
\
$_Senderliste_temp = "";;$_rowsnext_temp = 0;;$_rowsprime_temp = 0;;"Sender"|"ab"|"Aktuelle Sendung"|"ab"|"Naechste Sendung"|"ab"|"Sendung"\
TPL_TVMT(next,DasErste,01 ARD,https://www.ardmediathek.de/live/Y3JpZDovL2Rhc2Vyc3RlLmRlL2xpdmUvY2xpcC9hYmNhMDdhMy0zNDc2LTQ4NTEtYjE2Mi1mZGU4ZjY0NmQ0YzQ)\
TPL_TVMT(next,ZDF,02 ZDF,https://www.zdf.de/live-tv)\
TPL_TV(next,RTL,04 RTL)\
TPL_TV(next,RTL2,05 RTL2)\
TPL_TV(next,Vox,06 Vox)\
TPL_TV(next,VOXup,.. VOXup)\
TPL_TVMT(next,ProSieben,07 ProSieben,https://video.prosieben.de/livestreams/1)\
TPL_TVMT(next,Sat1,08 Sat1,https://video.prosieben.de/livestreams/2)\
TPL_TVMT(next,KabelEins,09 KabelEins,https://video.prosieben.de/livestreams/3)\
TPL_TVMT(next,3sat,10 3Sat,https://www.ardmediathek.de/live/Y3JpZDovLzNzYXQuZGUvTGl2ZXN0cmVhbS0zc2F0)\
TPL_TVMT(next,ARTE,11 Arte,https://www.ardmediathek.de/live/Y3JpZDovL2FydGUuZGUvTGl2ZXN0cmVhbS1BUlRF)\
TPL_TV(next,DMax,12 D-Max)\
TPL_TVMT(next,ServusHD,13 ServusTV,https://www.servustv.com/allgemein/p/jetzt-live/119753/)\
TPL_TVMT(next,NDRFernsehen,14 NDR,https://www.ardmediathek.de/live/Y3JpZDovL25kci5kZS9MaXZlc3RyZWFtLU5EUi1OaWVkZXJzYWNoc2Vu)\
TPL_TVMT(next,WDRFernsehen,15 WDR3,https://www.ardmediathek.de/live/Y3JpZDovL3dkci5kZS9CZWl0cmFnLTNkYTY2NGRlLTE4YzItNDY1MC1hNGZmLTRmNjQxNDcyMDcyYg)\
TPL_TVMT(next,HRFernsehen,16 HR3,https://www.ardmediathek.de/live/Y3JpZDovL2hyLmRlL0xpdmVzdHJlYW0tSFI)\
TPL_TVMT(next,SWRFernsehen,17 SWR,https://www.ardmediathek.de/live/Y3JpZDovL3N3ci5kZS8xMzQ4MTA0Mg)\
TPL_TVMT(next,MDRS-Anhalt,18 MDR,https://www.ardmediathek.de/live/Y3JpZDovL21kci5kZS9MaXZlc3RyZWFtLU1EUi1TYWNoc2Vu)\
TPL_TVMT(next,BRFernsehen,BR3,https://www.ardmediathek.de/live/Y3JpZDovL2JyLmRlL0xpdmVzdHJlYW0tQlItU8O8ZA)\
TPL_TVMT(next,rbbBerlin,RBB,https://www.ardmediathek.de/live/Y3JpZDovL3JiYi1vbmxpbmUuZGUvcmJiZmVybnNlaGVuL2xpdmVfYnJhbmRlbmJ1cmcvc2VuZGVwbGF0ei0tLWxpdmVzdHJlYW0tLS1icmFuZGVuYnVyZy0tLWhsczE)\
TPL_TV(next,ComedyCentralVIVA,19 ComedyCentral)\
TPL_TVMT(next,ProSiebenMaxx,20 pro7maxx,https://video.prosieben.de/livestreams/5)\
TPL_TV(next,SuperRTL,21 SuperRTL)\
TPL_TV(next,RTLNitro,22 Nitro)\
TPL_TVMT(next,Tele5,23 Tele5,https://tele5.de/live/)\
TPL_TVMT(next,Sat1Gold,24 Sat1 Gold,https://video.prosieben.de/livestreams/6)\
TPL_TVMT(next,phoenix,25 phoenix,https://www.ardmediathek.de/live/Y3JpZDovL3Bob2VuaXguZGUvTGl2ZXN0cmVhbS1waG9lbml4)\
TPL_TVMT(next,KabelEinsDoku,202 Kabel1doku,https://video.prosieben.de/livestreams/7)\
TPL_TV(next,N24Doku,203 n24 Doku)\
TPL_TVMT(next,ZDFinfo,204 ZDF Info,https://www.zdf.de/live-tv)\
TPL_TV(next,Sport1HD,300 DSF)\
TPL_TV(next,Eurosport1,301 Eurosport)\
TPL_TV(next,WELT,302 Welt n24)\
TPL_TVMT(next,ntv,303 n-tv,https://www.n-tv.de/mediathek/livestream/24-Stunden-ntv-Livestream-article9511936.html)\
TPL_TVMT(next,tagesschau24,304 tagesschau24,https://www.ardmediathek.de/live/Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXUvbGl2ZXN0cmVhbQ)\
TPL_TVMT(next,History,history,https://www.history.de/history-play/history-play-auf-amazon-prime.html)\
TPL_TV(next,TLC,TLC)\
TPL_TVMT(next,Sixx,?? Sixx,https://video.prosieben.de/livestreams/4)\
TPL_TV(next,HGTV,428 HGTV)\
"&nbsp;;"|"&nbsp;;"|"&nbsp;;"|"&nbsp;;"|"&nbsp;;"|"&nbsp;;"|"&nbsp;;"\
"Sender"|"ab"|"Sendung"|"ab"|"Sendung"|"ab"|"Sendung"\
TPL_TV(prime,DasErste,ard)\
TPL_TV(prime,ZDF,zdf)\
TPL_TV(prime,Sat1,sat1)\
TPL_TV(prime,RTL,rtl)\
TPL_TV(prime,RTL2,rtl2)\
TPL_TV(prime,ProSieben,pro7)\
TPL_TV(prime,DMax,dmax)\
TPL_TV(prime,Vox,vox)\
TPL_TV(prime,KabelEins,kabel1)\
TPL_TV(prime,KabelEinsDoku,kabel1doku)\
TPL_TV(prime,KabelEinsClassic,kabel1classic)\
TPL_TV(prime,ProSiebenMaxx,pro7maxx)\
TPL_TV(prime,Sixx,sixx)\
TPL_TV(prime,ntv,ntv)\
TPL_TV(prime,N24Doku,n24)\
TPL_TV(prime,History,history)\
TPL_TV(prime,TLC,tlc)\
TPL_END()

Wie immer mit meinen Sendern und html-Formatierungen. Code-Formatierungen habe ich versucht, dein System beizubehalten.
Die Merge-Variante ohne ChannelFilter habe ich rausgenommen (readingsVal gibt eh eine Vorgabe zurück, die könnte man noch auf ".*" anpassen), die ohne src-Datei ist durch die Schleife überflüssig geworden.

Tante Edit hat noch eine wichtige Änderung:
attr event-on-update-reading nextparse
Da sich diese Werte meistens so um die 5 min tummeln, kann es schon mal passieren, dass es kein change, sondern nur ein update gibt. Dann müsste der _sik ran, der ja erst in einer Stunde zuschlägt.

mumpitzstuff

1.) rytecDE_Basic wird bei dir 2 mal geladen. Einmal am Anfang der merge Funktion und dann noch einmal wenn du die Schleife durchläufst.

2.) Deine merge Funktion dürfte nicht mehr ohne $_filterChannels = 1 funktionieren.

3.) Es gibt auch anderssprachige Programmlisten, die würden mit deiner Variante nicht funktionieren, da du genau 3 Dateien in deiner merge Funktion zulässt.

Ich habe mal eine etwas andere Variante gestrickt...

  sub tvMerge\
  {\
    my ($dstName, @srcNames) = @_;;\
    my $fh;;\
    my $data;;\
    my $start = '';;\
    my $channels = '';;\
    my $channels_flt = '';;\
    my $programms = '';;\
    my $programms_flt = '';;\
    my $end = '';;\
    my $pos;;\
\
    if (-e $dstName)\
    {\
      open($fh, '<', $dstName) or die "Can't open file $!";;\
      read($fh, $data, -s $fh);;\
      close($fh);;\
\
      if (-1 != ($pos = index($data, '<channel ')))\
      {\
        $start = substr($data, 0, $pos);;\
      }\
\
      if (-1 != ($pos = rindex($data, '</programme>')))\
      {\
        $end = substr($data, $pos + 12);;\
      }\
\
      for (my $i = 0;; $i < (scalar(@srcNames) + 1);; $i++)\
      {\
        if (0 != $i)\
        {\
          my $file = $srcNames[$i - 1];;\
\
          if (-e $file)\
          {\
            open($fh, '<', $file) or die "Can't open file $!";;\
            read($fh, $data, -s $fh);;\
            close($fh);;\
          }\
          else\
          {\
            last;;\
          }\
        }\
\
        while ($data =~ /(\s*<channel\s.*?id="(.*?)".*?<\/channel>)/sg)\
        {\
          if (0 != $_filterChannels)\
          {\
            $_ = $1;;\
\
            if ($2 =~ $_channelFilter)\
            {\
              $channels_flt .= $_;;\
            }\
          }\
          else\
          {\
            $channels .= $1;;\
          }\
        }\
\
        while ($data =~ /(\s*<programme\s.*?channel="(.*?)".*?<\/programme>)/sg)\
        {\
          if (0 != $_filterChannels)\
          {\
            $_ = $1;;\
\
            if ($2 =~ $_channelFilter)\
            {\
              $programms_flt .= $_;;\
            }\
          }\
          else\
          {\
            $programms .= $1;;\
          }\
        }\
      }\
\
      open($fh, '>', $dstName) or die "Can't open file $!";;\
\
      if (0 != $_filterChannels)\
      {\
        print $fh $start.$channels_flt.$programms_flt.$end;;\
      }\
      else\
      {\
        print $fh $start.$channels.$programms.$end;;\
      }\
\
      close($fh);;\
    }\
  }\


  sub tvDownloadMerge()\
  {\
    my $output = '';;\
\
    ## other server\
    ## http://www.xmltvepg.nl\
    ## http://91.121.106.172/~rytecepg/epg_data\
    ## http://rytecepg.epgspot.com/epg_data\
    ## http://epg.vuplus-community.net\
    ## datafiles: rytecDE_Basic.xz, rytecDE_Common.xz, rytecDE_SportMovies.xz\
    $output .= qx(wget $_server/rytecDE_Basic.xz -O $_path/rytecDE_Basic.xz 2>&1 || rm -f $_path/rytecDE_Basic.xz 2>&1);;\
    $output .= qx(xz -df $_path/rytecDE_Basic.xz 2>&1);;\
    $output .= qx(wget $_server/rytecDE_Common.xz -O $_path/rytecDE_Common.xz 2>&1 || rm -f $_path/rytecDE_Common.xz 2>&1);;\
    $output .= qx(xz -df $_path/rytecDE_Common.xz 2>&1);;\
    $output .= qx(wget $_server/rytecDE_SportMovies.xz -O $_path/rytecDE_SportMovies.xz 2>&1 || rm -f $_path/rytecDE_SportMovies.xz 2>&1);;\
    $output .= qx(xz -df $_path/rytecDE_SportMovies.xz 2>&1);;\
    ## download and merge other files here if needed\
\
    ## tvMerge($_dataFile, $_path.'/rytecDE_Common');;\
    tvMerge($_dataFile, $_path.'/rytecDE_Common', $_path.'/rytecDE_SportMovies');;\
\
    return $output;;\
  }\


tvMerge kann jetzt eine variable Anzahl von Übergabeparametern erhalten, die dann in der merge Funktion zusammen geführt werden. Intensivere Tests stehen noch aus, da ich das grad erst fertig gestellt habe. Deine Anregung doppelten Code zu entfernen ist aber tatsächlich sinnvoll, da es die Überschtlichkeit wesentlich erhöht. Das mit dem parsen schaue ich mir später auch noch mal an, da kann man, wie bei dir schon zu sehen, mit Sicherheit auch noch was kürzen.


Per

1. Weiss ich, könnte man mit einer Abfrage lösen. Aufwand ist es aber so oder so.
2. Ja, habe ich ja geschrieben. Könnte man ganz löschen und über den Defaultwertl lösen.
3. Sollte eigentlich beliebig viele Dateien ermöglichen. Zumindest ist es so konzipiert. Falls nur drei gehen, ist das ein Fehler. ABER: ich habe nur drei Namen drin stehen, weil ich nur die drei kenne.

Zu deiner neuen Variante kann ich aktuell nichts sagen, am Handy kann man das nicht sinnvoll lesen.

Per

Interessanterweise habe ich mit Download und Merge keine Probleme, sondern mit Parse. Es werden nicht alle Sender abgefragt. Aber da verbergen sich ja auch noch die meisten der 20% ;).

Tante Edit hat den Fehler gefungen.

Per

Die Lernkurve *) war da, wie beim Walter zwei Schritte vor und einen zurück :D

Jetzt arbeitet das Script zu meiner Zufriedenheit. Aus optischen Gründen würde ich noch die Breite der Sender- und der Zeitspalten fest vergeben, dann kann man auch zwei Tabellen draus machen ("<") und sich die ganze Berechnung und Speicherung der Zeilenzahl sparen. Allerdings: "last" scheint nur im Wiki zu existieren. Gibt man halt 100 ein, das reicht eine Weile.
Die Fehler beim Parsen und beim Aktualisieren der Anzeige sind behoben. Ein paar optische Kleinigkeiten (z.B. Platzhalter, wenn kein Mediathek und/oder Device-Eintag) noch und dann bin ich fertig.

*) unfold([$1:$2_$3_000_title],[$1:$2_$3_000_stitle]."\n\n".[$1:$2_$3_000_desc]) z.B. zeichnet die Tabelle dreimal neu, unfold("$2_$1_000") hingegen gar nicht. Meine Lösung: unfold("$2_$1_000",[$SELF:_$2_$1_000_1start]) was aber auch erfordert, dass dieses Reading nach den drei (im Original vier) anderen aktualisiert wird.

mumpitzstuff

Ich habe gestern mal etwas experimentiert und sobald ich etwas an der unfold Funktion ändere, werden Updates gar nicht mehr oder nur unvollständig angezeigt. Zu sehen ist das, wenn man sich eine zu ändernde Sendung raus sucht und dann auf das 5min Raster wartet. Wenn man da rum spielt, ist danach der Titel oder die Description falsch. Man muss also alle Dinge updaten. Zudem wird, meines Erachtens, nicht die ganze Tabelle neu gezeichnet, sondern nur die Zelle bzw. deren Inhalt.

Eventuell kann man damit aber noch Experimentieren: https://fhem.de/commandref_DE.html#DOIF_Zeitintervalle_Readings_und_Status_ohne_Trigger

Per

Updaten musst du natürlich alle, aber nur die letzte in der (zeitlichen) Reihe muss triggern.
Gestern kam aber z.B. eine Sendung auf RTL2 (?, "Shopping Queen"), da hatten (mind.) 3 Folgen den gleichen Titel, Subtitel und die gleiche Beschreibung.
Ergo: kein Trigger bei event-on-change-reading. Die Reit ändert sich aber immer. Wobei es ja kein Problem ist, wenn sich ohnehin nichts ändert, aber wenn man nur auf eins davon triggert und das falsche erwischt? Ich triggere auf die Startzeit und aktualisiere das Reading als letztes, dann passt das immer.

mumpitzstuff

Und wenn alles gleich ist, warum muss man dann triggern?

PS: Hier gibt es übrigens auch noch ein ganz nettes Programmpaket, allerdings sieht es so aus, als ob man sich das täglich downloaden müsste. https://iptv-org.github.io/

Per

Interessant, aber dann sitze ich nur noch vorm PC :D

Das tägliche Downloaden wäre doch mit Fhem gar kein Problem...

mumpitzstuff

Ich habe jetzt im ersten Beitrag meine vorerst finale Version abgelegt. Hier habe ich noch die Anzahl der Readings auf 3 von ursprünglich 5 pro Eintrag reduziert. In der Version von Per waren bereits Date und Time zu einem Reading zusammen geführt worden, was ich auch übernommen habe. Zusätzlich dazu habe ich noch stitle und desc zusammengeführt.

Per

Letzteres hatte ich auch überlegt, aber da dort ein <br> als Trenner reinkommt, dürfte der (optische) Platzbedarf sich nicht verkleinern.
defmod TV_Programm DOIF subs\
{\
  \
  use utf8;;\
  use Date::Parse;;\
  # sudo apt-get install libxml-bare-perl\
  use XML::Bare 0.53 qw(forcearray);;\
  use Blocking;;\
  $_name = "$SELF";;\
  $_path = qx(pwd 2>&1);;\
  $_path =~ s/\/?\s/\//;;\
  #'/opt/fhem/';;\
  $_data = '/rytecDE_filt_'.$_name;;\
  $_dataFile = $_path.$_data;;\
  #use Encode qw(encode_utf8 decode_utf8);;\
  \
  ### CONFIG AREA ###\
  $_server = AttrVal($_name,"server","http://epg.vuplus-community.net");; \
  my $channelFilter = ReadingsVal($_name,"sender","ZDF\\.|");;\
  $channelFilter =~ s/\|$//;;\
  $_channelFilter = qr/^(?:$channelFilter)/;;\
  # telnet port must not be password protected (open)\
  # this is used as fallback if telnet port cannot created automatically\
  $_telnetPort = 7072;;\
  # can be used to adjust the program times (mostly not needed!)\
  $_timeAdjust = 0;;\
  # enable/disable unused channel filtering on filemerge (enabled = small file = faster)\
  $_filterChannels = 1;;\
  # enable/disable updates based on starttimes (enabled = update channels only if needed = faster)\
  $_updateBasedOnStarttimes = 1;;\
  # enable/disable use of Time::Piece (timepiece is faster but not instaled on some systems)\
  $_timepiece = 0;;\
  \
  # internal variables\
  $_startTimes = ();;\
\
  sub filterText($)\
  {\
    my $text = shift;;\
    \
    $text =~ s/[\x{0022}\x{0060}\x{003b}\x{0027}\"\`;;\'\r]//g;;\
    #$text =~ s/[\"\`;;\'\r]//g;;\
    $text =~ s/[\n]/<br>/g;;\
    \
    return $text;;\
  }\
\
  sub xmltv2epoch($)\
  {\
    my $t = shift;;\
    \
    if ($_timepiece)\
    {\
      use Time::Piece;;\
      \
      # fast version\
      return Time::Piece->strptime($t, '%Y%m%d%H%M%S %z')->epoch;;\
    }\
    else\
    {\
      # slow but compatible version\
      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 createTelnet($)\
  {\
    my $device = shift;;\
    my $telnet = undef;;\
    \
    foreach my $d (sort keys %::defs)\
    {\
      next if ($d !~ /telnetForTvUpdateFn_\d+/);;\
      my $h = $::defs{$d};;\
      next if (!$h->{TYPE} || $h->{TYPE} ne 'telnet' || $h->{SNAME});;\
      next if (::AttrVal($d, 'allowfrom', '127.0.0.1') ne '127.0.0.1');;\
      next if ($h->{DEF} !~ /^\d+( global)?$/);;\
      next if ($h->{DEF} =~ /IPV6/);;\
      \
      $telnet = $d;;\
      last;;\
    }\
    \
    if (!defined($telnet))\
    {\
      $telnet = 'telnetForTvUpdateFn_'.time();;\
      my $ret = ::CommandDefine(undef, "-temporary $telnet telnet 0");;\
      \
      if (defined($ret))\
      {\
        Log3 $device, 1, $device.': Cannot create telnet port ('.$ret.')';;\
        return undef;;\
      }\
      \
      $::attr{$telnet}{room} = 'hidden';;\
      $::attr{$telnet}{allowfrom} = '127.0.0.1';;\
    }\
    \
    return $::defs{$telnet}{PORT};;\
  }\
\
  sub sendTelnet($$)\
  {\
    my ($name, $sendTelnet) = @_;;\
    if ($sendTelnet)\
    {\
      my $port;;\
      $port = createTelnet($name);;\
      $port = $_telnetPort if (!defined($port));;\
      `perl /opt/fhem/fhem.pl $port "$sendTelnet"`;;\
    }\
  }\
\
  sub DOIF::doDownload($)\
  {\
    my ($name, $data) = split("\\|", shift);;\
    my $output = '';;\
    $output .= qx(wget $_server/$data.xz -O $_path/$data.xz 2>&1);;\
    $output .= qx(xz -df $_path/$data.xz 2>&1);;\
    \
    return $name.'|'. $data.'|'.$output;;\
  }\
\
  sub DOIF::endDownload($)\
  {\
    my ($name, $data, $output) = split("\\|", shift);;\
    \
    Log3 $name, 5, $name.': '.$output;;\
    Log3 $name, 4, $name.': Blocking call finished to download tv data.';;\
    \
    my $blockname = "PID_DOWNLOAD".$data;;\
    delete($_blockingcalls{$blockname});;\
    $_lastdown = time();;\
    sendTelnet($name,";;setreading $name $data $_lastdown;;");;\
  }\
\
  sub DOIF::abortDownload($)\
  {\
    my ($name, $data) = split("\\|", shift);;\
    \
    my $blockname = "PID_DOWNLOAD".$data;;\
    delete($_blockingcalls{$blockname});;\
    Log3 $name, 1, $name.': Blocking call aborted (download).';;\
    $_lastdown = 0;;\
    sendTelnet($name,";;setreading $name $data $_lastdown;;");;\
  }\
\
  sub startDownload($)\
  {\
    my $fileName = shift;;\
    my $dataFile = $_path.$fileName;;\
    \
    # prevent download spamming\
    if (-e $dataFile)\
    {\
      my $btime = ((time() - (stat($dataFile))[9]) / 60.0 / 60.0 / 24.0);;\
      \
      if ($btime < 1.0)\
      {\
        Log3 $_name, 1, $_name.': Download of '.$fileName.' skipped because file is not older than 1 day ('.($btime).').';;\
        return;;\
      }\
    }\
    \
    my $blockname = "PID_DOWNLOAD".$fileName;;\
    if (defined($_blockingcalls{$blockname}))\
    {\
      Log3 $_name, 3, $_name.': Blocking call already running (download).';;\
      ::BlockingKill($_blockingcalls{$blockname});;\
    }\
    \
    $_blockingcalls{$blockname} = ::BlockingCall('DOIF::doDownload', $_name.'|'.$fileName, 'DOIF::endDownload', 300, 'DOIF::abortDownload', $_name.'|'.$fileName);;\
  }\
\
  sub DOIF::doMerge($)\
  {\
    my ($name, $data) = split("\\|", shift);;\
    my $output = '';;\
    \
    my @srcName = ('rytecDE_Basic','rytecDE_Common','rytecDE_SportMovies');;\
    my $fh;;\
    my $dst;;\
    my $src;;\
    my $start = '';;\
    my $channels = '';;\
    my $programms = '';;\
    my $end = '';;\
    my $pos;;\
    my $ftime = 2;;\
    \
    open($fh, '<', $_path.$srcName[0]) or die "Can't open file $!";;\
    read($fh, $dst, -s $fh);;\
    close($fh);;\
    \
    if (-1 != ($pos = index($dst, '<channel ')))\
    {\
      $start = substr($dst, 0, $pos);;\
    }\
    \
    if (-1 != ($pos = rindex($dst, '</programme>')))\
    {\
      $end = substr($dst, $pos + 12);;\
    }\
    \
#    if (0 != $_filterChannels)\
    {\
      my $FilesCount = (@srcName);;\
      for ($i=0;;$i<$FilesCount;;$i++)\
      {\
$src = $srcName[$i];;\
if (-e $src)\
{\
          open($fh, '<', $src) or die "Can't open file $!";;\
          read($fh, $src, -s $fh);;\
          close($fh);;\
          while ($src =~ /(\s*<channel\s.*?id="(.*?)".*?<\/channel>)/sg)\
          {\
            $_ = $1;;\
            \
            if ($2 =~ $_channelFilter)\
            {\
              $channels .= $_;;\
            }\
          }\
          \
          while ($src =~ /(\s*<programme\s.*?channel="(.*?)".*?<\/programme>)/sg)\
          {\
            $_ = $1;;\
            \
            if ($2 =~ $_channelFilter)\
            {\
              $programms .= $_;;\
            }\
          }\
        }\
      }\
    }\
    open($fh, '>', $_dataFile) or die "Can't open file $!";;\
    print $fh $start.$channels.$programms.$end;;\
    close($fh);;\
    \
    return $name.'|'.$output;;\
  }\
\
  sub DOIF::endMerge($)\
  {\
    my ($name, $output) = split("\\|", shift);;\
    \
    Log3 $name, 5, $name.': '.$output;;\
    Log3 $name, 4, $name.': Blocking call finished to download tv data.';;\
    \
    delete($_blockingcalls{PID_MERGE});;\
    $_lastmerge = time();;\
    sendTelnet($name,";;setreading $name lastmerge $_lastmerge;;");;\
  }\
\
  sub DOIF::abortMerge($)\
  {\
    my $name = shift;;\
    \
    delete($_blockingcalls{PID_MERGE});;\
    \
    Log3 $name, 1, $name.': Blocking call aborted (download).';;\
    $_lastmerge = 0;;\
    sendTelnet($name,";;setreading $name lastmerge $_lastmerge;;");;\
  }\
\
  sub startMerge($)\
  {\
    my $name = shift;;\
    \
    my $channelFilter = ReadingsVal($_name,"sender","ZDF\\.|");;\
    $channelFilter =~ s/\|$//;;\
    $_channelFilter = qr/^(?:$channelFilter)/;;\
    \
    if (defined($_blockingcalls{PID_DOWNLOADrytecDE_Basic})\
    or defined($_blockingcalls{PID_DOWNLOADrytecDE_Common})\
    or defined($_blockingcalls{PID_DOWNLOADrytecDE_SportMovies}))\
    {\
      set_Exec('init_merge', 10, 'startMerge("$SELF")');;\     
      return;;\
    }\
    if (defined($_blockingcalls{PID_MERGE}))\
    {\
      Log3 $name, 3, $name.': Blocking call already running (merge).';;\
      \
      ::BlockingKill($_blockingcalls{PID_MERGE});;\
    }\
    \
    $_lastmerge = 0;;\
    sendTelnet($name,";;setreading $name lastmerge $_lastmerge;;");;\
    \
    $_blockingcalls{PID_MERGE} = ::BlockingCall('DOIF::doMerge', $name.'|'.$_data, 'DOIF::endMerge', 300, 'DOIF::abortMerge', $name);;\
  }\
\
\
  sub tvParse($$$)\
  {\
    my ($device, $mode, $port) = @_;;\
    my $obj;;\
    my $xml;;\
    my $lastChannel = '';;\
    my $reading = '';;\
    my $n = 999;;\
    my $p = 999;;\
    my $primeTime = substr(FmtDateTime(time() + $_timeAdjust), 0, 11).'20:14:00';;\
    my $nextparse = time() + 3600;;\
    my $start;;\
    my $stop;;\
    my $startform;;\
    my $readingValue;;\
    my $old = time() + $_timeAdjust;;\
    \
    ## check if any update is needed\
    if ((0 != $_updateBasedOnStarttimes) && ('prime' ne $mode) && keys(%{$_startTimes}))\
    {\
      my $nothingTodo = 1;;\
      foreach (keys(%{$_startTimes}))\
      {\
        if ($_startTimes{$_} <= $old)\
        {\
          $nothingTodo = 0;;\
          Log3 $device, 4, $device.': Update is not blocked because at least one actual program is finished (reading: '.$_.', start: '.$_startTimes{$_}.', old: '.$old.').';;\
          last;;\
        }\
      }\
      \
      if (0 != $nothingTodo)\
      {\
        Log3 $device, 4, $device.': Update is blocked because no actual program was finished.';;\
        return %{$_startTimes};;\
      }\
    }\
    \
    $obj = XML::Bare->new(file => $_dataFile);;\
    $xml = $obj->parse();;\
    \
    if (!$@)\
    {\
      \
      foreach (@{forcearray($xml->{'tv'}{'programme'})})\
      {\
        my $stop = xmltv2epoch($_->{'stop'}{'value'});;\
        \
           # filter old stuff\
        if ($stop > $old)\
        {\
          if ($lastChannel ne $_->{'channel'}{'value'})\
          {\
            $lastChannel = $reading = $_->{'channel'}{'value'};;\
            $reading =~ s/[\.\s]//g;;\
            $reading =~ s/de$//;;\
            $p = 0;;\
    \
            $nextparse = $stop if (($nextparse > $stop) and ($stop > (time() + 30)));;\
            if ((0 == $_updateBasedOnStarttimes) || !exists($_startTimes{$reading}) || ($_startTimes{$reading} <= $old))\
            {\
              $n = 0;;\
      $_startTimes{$reading} = $stop if (0 != $_updateBasedOnStarttimes);;\
            }\
            else\
            {\
            Log3 $device, 4, $device.': '.$reading.' is blocked because actual program is not finished (reading: '.$reading.', start: '.$_startTimes{$reading}.', old: '.$old.').';;\
#              Log3 $device, 4, $device.': '.$reading.' is blocked because actual program is not finished (start: '.$_startTimes{$reading}.', old: '.$old.').';;\
            }\
          }\
  \
          $start = xmltv2epoch($_->{'start'}{'value'});;\
      $startform = substr(FmtDateTime($start), 0, 19);;\
  \
          if (($p < 3 && 'prime' eq $mode && $startform gt $primeTime) or ($n < 3 && 'next' eq $mode))\
          {\
            my $setreading = ";;setreading $device _$reading";;\
    my $sendTelnet = '';;\
    if ('prime' eq $mode)\
    {\
              $setreading .= "_prime_".sprintf("%03d", $p++);;\
    }\
    else\
    {\
              $setreading .= "_next_".sprintf("%03d", $n++);;\
    }\
    \
            $readingValue = filterText(@{forcearray($_->{'title'})}[0]->{'value'});;\
            $sendTelnet .= $setreading."_2title $readingValue";;\
    \
            $readingValue = ((exists($_->{'sub-title'}{'value'})) ? filterText($_->{'sub-title'}{'value'}) : 'na');;\
            $sendTelnet .= $setreading."_3stitle $readingValue";;\
    \
            $readingValue = ((exists($_->{'desc'}{'value'})) ? filterText($_->{'desc'}{'value'}) : 'na');;\
            $sendTelnet .= $setreading."_4desc $readingValue";;\
    \
            $readingValue = $startform;;\
            $sendTelnet .= $setreading."_1start $readingValue";;\
    \
              #Log3 $device, 5, $device.': '.encode_utf8($sendTelnet);;\
            \
            `perl /opt/fhem/fhem.pl $port "$sendTelnet"`;;\
            \
          }\
        }\
      }\
      \
      if ('next' eq $mode)\
      {\
my $sendTelnet .= ";;setreading $device nextparse ". ($nextparse - time() + 10);;\
         #Log3 $device, 5, $device.': '.encode_utf8($sendTelnet);;\
        \
        `perl /opt/fhem/fhem.pl $port "$sendTelnet"`;;\
      }\
    }\
    \
    return %{$_startTimes};;\
  }\
\
  sub DOIF::doParse($)\
  {\
    my ($name, $mode, $port) = split("\\|", shift);;\
    my $ret = $name.'|'.$mode;;\
    my %startTimes = tvParse($name, $mode, $port);;\
    \
    foreach (keys(%startTimes))\
    {\
      $ret .= '|'.$_.'|'.$startTimes{$_};;\
    }\
    \
    return $ret;;\
  }\
\
  sub DOIF::endParse($)\
  {\
    my ($name, $mode, @newStartTimes) = split("\\|", shift);;\
    \
    for (my $i = 0;; $i < scalar(@newStartTimes);; $i += 2)\
    {\
      $_startTimes{$newStartTimes[$i]} = $newStartTimes[$i + 1];;\
    }\
    \
    Log3 $name, 4, $name.': Blocking call finished to parse tv data.';;\
    \
    my $testparse = "PID_PARSE".$mode;;\
    delete($_blockingcalls{$testparse});;\
  }\
\
  sub DOIF::abortParse($)\
  {\
    my ($name, $mode) = split("\\|", shift);;\
    \
    Log3 $name, 1, $name.': Blocking call aborted (parse).';;\
    \
    my $testparse = "PID_PARSE_".$name.$mode;;\
    delete($_blockingcalls{$testparse});;\
  }\
\
  sub startParse($$)\
  {\
    my ($name, $mode) = @_;;\
    \
    if (defined($_blockingcalls{PID_MERGE}))\
    {\
      return;;\
    }\
    my $testparse = "PID_PARSE_".$name.$mode;;\
    if (defined($_blockingcalls{$testparse}))\
    {\
      Log3 $name, 3, $name.': Blocking call already running (parse).';;\
      ::BlockingKill($_blockingcalls{$testparse});;\
    }\
    \
    my $port;;\
    $port = createTelnet($name);;\
    $port = $_telnetPort if (!defined($port));;\
    \
    $_blockingcalls{$testparse} = ::BlockingCall('DOIF::doParse', $name.'|'.$mode.'|'.$port, 'DOIF::endParse', 300, 'DOIF::abortParse', $name.'|'.$mode);;\
  }\
}\
\
init\
{\
  fhem("set $SELF down");;\
  if ([?$SELF:nextparse:sec] > 86400)\
  {\
    set_Exec('init_merge', 20, 'startMerge("$SELF")');;\
  }\
  else\
  {\
    set_Exec('init_next', 20, 'startParse("$SELF", "next")');;\
  }\
  set_Exec('init_next_sik', 7200, 'startParse("$SELF", "next")');;\
}\
down\
{\
  [00:05|Mo Do];;\
  set_Exec('init_down_Basic', 1, 'startDownload("rytecDE_Basic")');;\
  set_Exec('init_down_Common', 2, 'startDownload("rytecDE_Common")');;\
#  set_Exec('init_down_Sport', 3, 'startDownload("rytecDE_SportMovies")');;\
}\
merge\
{\
  [01:05|Mo Do];;\
  set_Exec('init_merge', 10, 'startMerge("$SELF")');;\
}\
parse_next_manu\
{\
  [$SELF:lastmerge];;\
  set_Exec('init_next', 20, 'startParse("$SELF", "next")');;\
}\
parse_next\
{\
  set_Exec('init_next', [$SELF:nextparse], 'startParse("$SELF", "next")');;\
  set_Exec('init_next_sik', 3601, 'startParse("$SELF", "next")');;\
}\
parse_prime\
{\
  [00:30];;\
  [$SELF:lastmerge];;\
  set_Exec('init_prime', 30, 'startParse("$SELF", "prime")');;\
}\
_refill\
{\
#  if ([$SELF:sender])\
  {\
    if (ReadingsVal("$SELF","sender","ZDF\\.") ne $_Senderliste)\
    {\
      fhem("deletereading $SELF .*(next|prime)_.*  200000");;\
      fhem("setreading $SELF sender $_Senderliste");;\
      fhem("set $SELF down");;\
      set_Exec('init_merge', 20, 'startMerge("$SELF")');;\
    }\
    fhem("setreading $SELF rowsnext $_rowsnext");;\
    fhem("setreading $SELF rowsprime $_rowsprime");;\
    \
  }\
}
attr TV_Programm userattr server:http://epg.vuplus-community.net,http://www.xmltvepg.nl,http://91.121.106.172/~rytecepg/epg_data,http://www.vuplus-community.net/rytec
attr TV_Programm alias Aktuelles TV-Programm
attr TV_Programm event-on-change-reading .*
attr TV_Programm event-on-update-reading nextparse
attr TV_Programm room Wohnzimmer
attr TV_Programm server http://www.xmltvepg.nl
attr TV_Programm uiTable\
{\
  package ui_Table;;\
  \
  $SHOWNOSTATE = 1;;\
  $ATTRIBUTESFIRST = 1;;\
  my $rowsnext = 100;; ##::ReadingsVal("$SELF","rowsnext",0);;\
  my $rowsprime = 100;; ##::ReadingsVal("$SELF","rowsprime",0);;\
  my $lastrow = 105;; ##$rowsnext + $rowsprime + 2;;\
  \
  $TR{0} = "style='color:red;;text-align:center;;font-weight:bold;;font-size:18px;;'";;\
  $TR{($rowsnext + 2)} = "style='color:red;;text-align:center;;font-weight:bold;;font-size:18px;;'";;\
  $TR{1..($rowsnext),($rowsnext + 4)..$lastrow} = "style='font-size:16px'";;\
  $TR{($rowsnext + 1)} = "style='border-top-style:solid;;border-bottom-style:solid;;border-color:#CCCCCC;;border-top-width:1px;;border-bottom-width:1px;;'";;\
#  $TD{0..$lastrow}{0} = ##"align='center'";;\
  $TD{0..$lastrow}{0} = "style='width:150px;;'";;\
  $TD{0..$lastrow}{2} = "style='width:22px;;'";;\
  $TD{0..$lastrow}{1,4,6} = "style='width:50px;;'";;\
  $TD{0..$lastrow}{3,5,7} = "style='width:25%;;'";;\
  $TD{0..$rowsnext, ($rowsnext + 3)..$lastrow}{1,4,6} = "style='border-left-style:solid;;border-color:#CCCCCC;;border-left-width:1px;;'";;\
  \
  sub showIcon\
  {\
    my ($mode, $icon, $show, $device, $state) = @_;;\
    $_rowsprime_temp += ($mode eq "prime" ? 1 : 0);;\
    $_rowsnext_temp += ($mode eq "next" ? 1 : 0);;\
    $_Senderliste_temp =~ s/$icon\\.\|//;;\
    $_Senderliste_temp .= $icon . "\\.|";;\
    \
    if ($device && $state)\
    {\
      return "<a href=\"". ::ReadingsVal("$SELF","$device","") =~ s/.state/$state/r ."\" target=\"IPTV\">".ICON("$show")."</a>";;\
#      return "<a href=\"$::FW_ME?cmd=set $device $state$::FW_CSRF\">".ICON("tv/$icon")."</a>";;\
    }\
    else\
    {\
      if ($device)\
      {\
        return ICON("$show")."</td><td>"."<a href=\"$device\" target=\"IPTV\">".ICON("tv/20px-Icon-video-library")."</a> ";;\
      }\
      else\
      {\
        return ICON("$show");;\
      }\
    }\
  }\
\
  sub unfold\
  {\
    my ($ReadingPre,$akt) = @_;;\
    my $title = ::ReadingsVal("$SELF","_${ReadingPre}_2title","-");;\
    my $desc = ::ReadingsVal("$SELF","_${ReadingPre}_3stitle","na")."\n\n". ::ReadingsVal("$SELF","_${ReadingPre}_4desc","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;;\
#    my $href = ($link ? "<a href=\"$link\" target=\"IPTV\">".ICON("tv/20px-Icon-video-library")."</a>" : "");;\
    return "<a href=\"#!\" onclick=\"FW_okDialog(';$title<br>$desc';)\">$title</a>";;\
  }\
\
  sub MT\
  {\
    my ($link) = @_;;\
    return "<a href=\"$link\" target=\"IPTV\">".ICON("tv/20px-Icon-video-library")."</a>" ;;\
  }\
\
  sub vars_reset\
  {\
    $_Senderliste_temp = "";; $_rowsprime_temp = 0;;$_rowsnext_temp = 0;;return "Sender";;\
  }\
\
  sub vars_save\
  {\
    $_Senderliste = $_Senderliste_temp;;\
    $_rowsprime = $_rowsprime_temp;;\
    $_rowsnext = $_rowsnext_temp;;\
    return ;;\
  }\
}\
##\
## parameter: mode (next or prime), channel name (see xml data file), icon name (filename of channel logo), device name for set command, command\
DEF TPL_TVSET(showIcon("$1", "$2", "$3", "$4", "$5")|substr([$SELF:_$2_$1_000_1start],11,5)|unfold("$2_$1_000",[$SELF:_$2_$1_000_1start])|substr([$SELF:_$2_$1_001_1start],11,5)|unfold("$2_$1_001",[$SELF:_$2_$1_000_1start])|substr([$SELF:_$2_$1_002_1start],11,5)|unfold("$2_$1_002",[$SELF:_$2_$1_002_1start]))\
##\
## parameter: mode (next or prime), channel name (see xml data file), icon name (filename of channel logo)\
DEF TPL_TV(showIcon("$1", "$2", "$3")|substr([$SELF:_$2_$1_000_1start,"01234567890  -  "],11,5)|" "|unfold("$2_$1_000",[$SELF:_$2_$1_000_1start,"01234567890  -  "])|substr([$SELF:_$2_$1_001_1start,"01234567890  -  "],11,5)|unfold("$2_$1_001",[$SELF:_$2_$1_001_1start,"01234567890  -  "])|substr([$SELF:_$2_$1_002_1start,"01234567890  -  "],11,5)|unfold("$2_$1_002",[$SELF:_$2_$1_002_1start]))\
##\
## parameter: mode (next or prime), channel name (see xml data file), icon name (filename of channel logo), URL Mediathek\
DEF TPL_TVMT(showIcon("$1", "$2", "$3")|substr([$SELF:_$2_$1_000_1start,"01234567890  -  "],11,5)|MT("$4")|unfold("$2_$1_000",[$SELF:_$2_$1_000_1start,"01234567890  -  "])|substr([$SELF:_$2_$1_001_1start,"01234567890  -  "],11,5)|unfold("$2_$1_001",[$SELF:_$2_$1_001_1start,"01234567890  -  "])|substr([$SELF:_$2_$1_002_1start,"01234567890  -  "],11,5)|unfold("$2_$1_002",[$SELF:_$2_$1_002_1start]))\
##\
DEF TPL_START(vars_reset()|"ab"|" "|"Aktuelle Sendung"|"ab"|"N&auml;;chste Sendung"|"ab"|"Sendung")\
DEF TPL_END(vars_save())\
##\
TPL_START()\
TPL_TVMT(next,DasErste,01 ARD,https://www.ardmediathek.de/live/Y3JpZDovL2Rhc2Vyc3RlLmRlL2xpdmUvY2xpcC9hYmNhMDdhMy0zNDc2LTQ4NTEtYjE2Mi1mZGU4ZjY0NmQ0YzQ)\
TPL_TVMT(next,ZDF,02 ZDF,https://www.zdf.de/live-tv)\
TPL_TV(next,RTL,04 RTL)\
TPL_TV(next,RTL2,05 RTL2)\
TPL_TV(next,Vox,06 Vox)\
TPL_TV(next,VOXup,.. VOXup)\
TPL_TVMT(next,ProSieben,07 ProSieben,https://video.prosieben.de/livestreams/1)\
TPL_TVMT(next,Sat1,08 Sat1,https://video.prosieben.de/livestreams/2)\
TPL_TVMT(next,KabelEins,09 KabelEins,https://video.prosieben.de/livestreams/3)\
TPL_TVMT(next,3sat,10 3Sat,https://www.ardmediathek.de/live/Y3JpZDovLzNzYXQuZGUvTGl2ZXN0cmVhbS0zc2F0)\
TPL_TVMT(next,ARTE,11 Arte,https://www.ardmediathek.de/live/Y3JpZDovL2FydGUuZGUvTGl2ZXN0cmVhbS1BUlRF)\
TPL_TV(next,DMax,12 D-Max)\
TPL_TVMT(next,ServusHD,13 ServusTV,https://www.servustv.com/allgemein/p/jetzt-live/119753/)\
TPL_TVMT(next,NDRFernsehen,14 NDR,https://www.ardmediathek.de/live/Y3JpZDovL25kci5kZS9MaXZlc3RyZWFtLU5EUi1OaWVkZXJzYWNoc2Vu)\
TPL_TVMT(next,WDRFernsehen,15 WDR3,https://www.ardmediathek.de/live/Y3JpZDovL3dkci5kZS9CZWl0cmFnLTNkYTY2NGRlLTE4YzItNDY1MC1hNGZmLTRmNjQxNDcyMDcyYg)\
TPL_TVMT(next,HRFernsehen,16 HR3,https://www.ardmediathek.de/live/Y3JpZDovL2hyLmRlL0xpdmVzdHJlYW0tSFI)\
TPL_TVMT(next,SWRFernsehen,17 SWR,https://www.ardmediathek.de/live/Y3JpZDovL3N3ci5kZS8xMzQ4MTA0Mg)\
TPL_TVMT(next,MDRS-Anhalt,18 MDR,https://www.ardmediathek.de/live/Y3JpZDovL21kci5kZS9MaXZlc3RyZWFtLU1EUi1TYWNoc2Vu)\
TPL_TVMT(next,BRFernsehen,BR3,https://www.ardmediathek.de/live/Y3JpZDovL2JyLmRlL0xpdmVzdHJlYW0tQlItU8O8ZA)\
TPL_TVMT(next,rbbBerlin,RBB,https://www.ardmediathek.de/live/Y3JpZDovL3JiYi1vbmxpbmUuZGUvcmJiZmVybnNlaGVuL2xpdmVfYnJhbmRlbmJ1cmcvc2VuZGVwbGF0ei0tLWxpdmVzdHJlYW0tLS1icmFuZGVuYnVyZy0tLWhsczE)\
TPL_TV(next,ComedyCentralVIVA,19 ComedyCentral)\
TPL_TVMT(next,ProSiebenMaxx,20 pro7maxx,https://video.prosieben.de/livestreams/5)\
TPL_TV(next,SuperRTL,21 SuperRTL)\
TPL_TV(next,RTLNitro,22 Nitro)\
TPL_TVMT(next,Tele5,23 Tele5,https://tele5.de/live/)\
TPL_TVMT(next,Sat1Gold,24 Sat1 Gold,https://video.prosieben.de/livestreams/6)\
TPL_TVMT(next,phoenix,25 phoenix,https://www.ardmediathek.de/live/Y3JpZDovL3Bob2VuaXguZGUvTGl2ZXN0cmVhbS1waG9lbml4)\
TPL_TVMT(next,KabelEinsDoku,202 Kabel1doku,https://video.prosieben.de/livestreams/7)\
TPL_TV(next,N24Doku,203 n24 Doku)\
TPL_TVMT(next,ZDFinfo,204 ZDF Info,https://www.zdf.de/live-tv)\
TPL_TV(next,Sport1HD,300 DSF)\
TPL_TV(next,Eurosport1,301 Eurosport)\
TPL_TV(next,WELT,302 Welt n24)\
TPL_TVMT(next,ntv,303 n-tv,https://www.n-tv.de/mediathek/livestream/24-Stunden-ntv-Livestream-article9511936.html)\
TPL_TVMT(next,tagesschau24,304 tagesschau24,https://www.ardmediathek.de/live/Y3JpZDovL2Rhc2Vyc3RlLmRlL3RhZ2Vzc2NoYXUvbGl2ZXN0cmVhbQ)\
TPL_TVMT(next,History,history,https://www.history.de/history-play/history-play-auf-amazon-prime.html)\
TPL_TV(next,TLC,TLC)\
TPL_TVMT(next,Sixx,?? Sixx,https://video.prosieben.de/livestreams/4)\
TPL_TV(next,HGTV,428 HGTV)\
"&nbsp;;"|"&nbsp;;"|"&nbsp;;"|"&nbsp;;"|"&nbsp;;"|"&nbsp;;"|"&nbsp;;"<\
"Sender"|"ab"|" "|"Sendung"|"ab"|"Sendung"|"ab"|"Sendung"\
TPL_TV(prime,DasErste,ard)\
TPL_TV(prime,ZDF,zdf)\
TPL_TV(prime,Sat1,sat1)\
TPL_TV(prime,RTL,rtl)\
TPL_TV(prime,RTL2,rtl2)\
TPL_TV(prime,ProSieben,pro7)\
TPL_TV(prime,DMax,dmax)\
TPL_TV(prime,Vox,vox)\
TPL_TV(prime,KabelEins,kabel1)\
TPL_TV(prime,KabelEinsDoku,kabel1doku)\
TPL_TV(prime,KabelEinsClassic,kabel1classic)\
TPL_TV(prime,ProSiebenMaxx,pro7maxx)\
TPL_TV(prime,Sixx,sixx)\
TPL_TV(prime,ntv,ntv)\
TPL_TV(prime,N24Doku,n24)\
TPL_TV(prime,History,history)\
TPL_TV(prime,TLC,tlc)\
TPL_END()


Hier sind zwar noch die Berechnungen für die einzelnen Tabellenteile drin, genutzt werden sie aber nicht wirklich, weil die Tabelle bei mir zweigeteilt ist. Ob gut oder schlecht, die Breite der Spalten ist dafür fixiert.

mumpitzstuff

Ich habe den Link im ersten Beitrag angepasst. Könntest du bei Änderungen vielleicht deinen aktuell letzten Beitrag editieren bitte? Dann muss ich nicht jedes Mal den Link aktualisieren. Wenn ich zu deiner Version noch etwas dazu schreiben soll, dann lass es mich bitte wissen und ich schreibe es dazu.

Per

Aktuell kommt von mir jetzt weniger, weil ich erstmal Grundlagen (Wirkbereiche von Variablen, Ansprechen von Variablen, Syntax dafür u.a.) "studiere". Zwei Devices haben mir noch zu viele Wechselwirkungen.
Aber die Idee mit dem festen Link ist gut, da finde ich es auch eher ;) und der Board Server ist (etwas) weniger belastet.

Damian

Was mir gerade noch aufgefallen ist:

{\
  if ([00:00:30|Mo Do])\
  {\
    startDownload("$SELF");;\
  }\
\
  if ([+:05])\
  {\
    ## start in a raster of 5min\
    startParse("$SELF", 'next');;\
  }\
\
  if ([00:32:30])\
  {\
    ## start between next updates\
    startParse("$SELF", 'prime');;\
  }\
}


sollte besser in einzelne Blöcke aufgeteilt werden. So wird der ganze Block alle 5 Minuten unnötig mit allen drei ifs abgearbeitet.

besser:

{
  if ([00:00:30|Mo Do])
  {
    startDownload("$SELF");
  }
}
{
  if ([+:05])
  {
    ## start in a raster of 5min
    startParse("$SELF", 'next');
  }
}
{
  if ([00:32:30])
  {
    ## start between next updates
    startParse("$SELF", 'prime');
  }
}


noch besser

{[00:00:30|Mo Do];startDownload("$SELF")}
{[+:05];startParse("$SELF", 'next')}
{[00:32:30];startParse("$SELF", 'prime')}


Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

mumpitzstuff

Ziehe ich die nächsten Tage gerade. Vielen Dank für die Information!!!

Ich habe auch noch eine Option eingebaut (ein- und ausschaltbar), das alle Daten im RAM gehalten werden, dann fällt das Laden und Parsen der XML Datei alle 5min auch noch weg... Das Laden der Datei ist dann aber leider blocking, allerdings auch nur genau 2x die Woche um 0:30 Uhr.