From 0c16e28584a37126705566a2325c59ad362799c3 Mon Sep 17 00:00:00 2001 From: KernSani Date: Fri, 14 Feb 2020 21:51:19 +0000 Subject: [PATCH] 98_DSBMobile.pm: Initial version git-svn-id: https://svn.fhem.de/fhem/trunk@21200 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/98_DSBMobile.pm | 891 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 891 insertions(+) create mode 100644 fhem/FHEM/98_DSBMobile.pm diff --git a/fhem/FHEM/98_DSBMobile.pm b/fhem/FHEM/98_DSBMobile.pm new file mode 100644 index 000000000..1848d5da0 --- /dev/null +++ b/fhem/FHEM/98_DSBMobile.pm @@ -0,0 +1,891 @@ +# $Id$ +############################################################################## +# +# 98_DSBMobile.pm +# An FHEM Perl module that supports parental control for Nintendo Switch +# +# Copyright by KernSani +# +# Fhem is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +############################################################################## +# Changelog: +############################################################################## +############################################################################## +# Todo: +# +# +############################################################################## + +package main; + +use strict; +use warnings; +use HttpUtils; +use Data::Dumper; +use FHEM::Meta; + +my $missingModul = ""; + +#eval "use Blocking;1" or $missingModul .= "Blocking "; +#eval "use UUID::Random;1" or $missingModul .= "install UUID::Random "; +eval "use IO::Compress::Gzip qw(gzip);1" or $missingModul .= "IO::Compress::Gzip "; +eval "use IO::Uncompress::Gunzip qw(gunzip);1" or $missingModul .= "IO::Compress::Gunzip "; +eval "use MIME::Base64;1" or $missingModul .= "MIME::Base64 "; +eval "use HTML::TableExtract;1" or $missingModul .= "HTML::TableExtract "; +eval "use HTML::TreeBuilder;1" or $missingModul .= "HTML::TreeBuilder "; + +##################################### +sub DSBMobile_Initialize($) { + my ($hash) = @_; + + $hash->{DefFn} = "DSBMobile_Define"; + $hash->{UndefFn} = "DSBMobile_Undefine"; + $hash->{GetFn} = "DSBMobile_Get"; + + #$hash->{SetFn} = "DSBMobile_Set"; + $hash->{AttrFn} = "DSBMobile_Attr"; + + my @DSBMobile_attr + = ( "dsb_user " + . "dsb_password " + . "dsb_class " + . "dsb_interval " + . "dsb_classReading " + . "dsb_outputFormat:textField-long " ); + $hash->{AttrList} = join( " ", @DSBMobile_attr ) . " " . $readingFnAttributes; + return FHEM::Meta::InitMod( __FILE__, $hash ); +} + +##################################### +sub DSBMobile_Define($@) { + my ( $hash, $def ) = @_; + + return $@ unless ( FHEM::Meta::SetInternals($hash) ); + + my @a = split( "[ \t][ \t]*", $def ); + + my $usage = "syntax: define DSBMobile"; + return "Cannot define device. Please install perl modules $missingModul." + if ($missingModul); + my ( $name, $type ) = @a; + if ( int(@a) != 2 ) { + return $usage; + } + + Log3 $name, 3, "[$name] DSBMobile defined $name"; + + $hash->{NAME} = $name; + + #start timer + if ( AttrNum( $name, "dsb_interval", 0 ) > 0 && $init_done ) { + my $next = int( gettimeofday() ) + 1; + InternalTimer( $next, 'DSBMobile_ProcessTimer', $hash, 0 ); + } +} +################################### +sub DSBMobile_Undefine($$) { + my ( $hash, $name ) = @_; + RemoveInternalTimer($hash); + return undef; +} +################################### +sub DSBMobile_Notify($$) { + my ( $hash, $dev ) = @_; + my $name = $hash->{NAME}; # own name / hash + my $events = deviceEvents( $dev, 1 ); + + return "" + if ( IsDisabled($name) ); # Return without any further action if the module is disabled + return if ( !grep( m/^INITIALIZED|REREADCFG$/, @{$events} ) ); + + my $next = int( gettimeofday() ) + 1; + InternalTimer( $next, 'DSBMobile_ProcessTimer', $hash, 0 ); +} +################################### +sub DSBMobile_Set($@) { + my ( $hash, $name, $cmd, @args ) = @_; + +} + +##################################### +sub DSBMobile_Get($@) { + my ( $hash, @a ) = @_; + my $name = $hash->{NAME}; + my $ret = ""; + my $usage = 'Unknown argument $a[1], choose one of timetable:noArg'; + + return "\"get $name\" needs at least one argument" + unless ( defined( $a[1] ) ); + + if ( $a[1] eq "timetable" ) { + $hash->{helper}{forceRead} = 1; + return DSBMobile_query($hash); + } + + # return usage hint + else { + return $usage; + } + return undef; +} + +##################################### +sub DSBMobile_query($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + Log3 $name, 5, "[$name] Getting data"; + + my $uuid = DSBMobileUUID(); + my $date = strftime "%Y-%m-%dT%H:%M:%S.999+0100", localtime time; + my $user = AttrVal( $name, "dsb_user", "" ); + my $pw = AttrVal( $name, "dsb_password", "" ); + + if ( $user eq "" or $pw eq "" ) { + return "User and password have to be maintained in the attributes"; + } + + my %arg = ( + "UserId" => $user, + "UserPw" => $pw, + "Language" => "de", + "Device" => "Nexus 4", + "AppId" => $uuid, + "appversion" => "2.5.9", + "OsVersion" => "27 8.1.0", + "PushId" => "", + "BundleId" => "de.heinekingmedia.dsbmobile", + "Date" => $date, + "LastUpdate" => $date + ); + + my $json = encode_json \%arg; + Log3 $name, 5, "[$name] Arguments (in json) to encode" . Dumper($json); + my $zip; + IO::Compress::Gzip::gzip \$json => \$zip; + my $b64 = encode_base64($zip); + my $body = '{"req": {"Data": "' . $b64 . '","DataType": 1}}'; + + my $header = { + 'Host' => 'app.dsbcontrol.de', + 'Content-Type' => 'application/json; charset=utf-8', + 'User-Agent' => 'DSBmobile/9759 (iPhone; iOS 13.3; Scale/3.00)', + 'Connection' => 'keep-alive', + 'Accept' => '*/*', + 'Accept-Language' => 'de-DE;q=1, en-DE;q=0.9', + 'Accept-Encoding' => 'gzip, deflate', + }; + my $param = { + header => $header, + url => 'https://www.dsbmobile.de/JsonHandler.ashx/GetData', + method => "POST", + hash => $hash, + data => $body, + callback => \&DSBMobile_getDataCallback + }; + Log3 $name, 5, "[$name] 1st nonblocking HTTP Call starting"; + HttpUtils_NonblockingGet($param); + return undef; +} + +##################################### +sub DSBMobile_getDataCallback($) { + my ( $param, $err, $data ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + + if ( $err ne "" ) { + Log3 $name, 3, "[$name] Error while requesting " . $param->{url} . " - $err"; + readingsSingleUpdate( $hash, "error", $err, 0 ); + return undef; + } + + Log3 $name, 5, "[$name] 1st nonblocking HTTP Call returning"; + Log3 $name, 5, "[$name] GetData - received $data"; + my $j = decode_json($data); + my $d64 = decode_base64( $j->{d} ); + my $json; + IO::Uncompress::Gunzip::gunzip \$d64 => \$json; + $json = latin1ToUtf8($json); + my $res = decode_json($json); + + Log3 $name, 5, "[$name] JSON received: " . Dumper($res); + my $url; + my $udate; + my $test = $res->{ResultMenuItems}[0]->{Childs}; + my @aus; + foreach my $c (@$test) { + + #if ($c->{Title} eq "Pläne") { + + #$ret .= Dumper($c->{root}{Childs}[0]->{Childs}[0]->{Detail}); + foreach my $topic ($c) { + if ( $c->{MethodName} eq "timetable" ) { + $url = $topic->{Root}{Childs}[0]->{Childs}[0]->{Detail}; + $udate = $topic->{Root}{Childs}[0]->{Childs}[0]->{Date}; + } + if ( $c->{MethodName} eq "tiles" ) { + my $d = $topic->{Root}{Childs}; + + for my $tile (@$d) { + my %au = ( + title => $tile->{Title}, + url => $tile->{Childs}[0]->{Detail}, + date => $tile->{Childs}[0]->{Date} + ); + push( @aus, \%au ); + } + } + } + } + + my ( $sec, $min, $hour, $mday, $month, $year, $wday, $yday, $isdst ) + = localtime( gettimeofday() ); + $month++; + $year += 1900; + my $today = sprintf( '%04d-%02d-%02d %02d:%02d', $year, $month, $mday, $hour, $min ); + + my ( $d, $m, $y, $h, $mi ) = $udate =~ /^([0-9]{2})\.([0-9]{2})\.([0-9]{4})\s([0-9]{2}):([0-9]{2})/; + $udate = sprintf( '%04d-%02d-%02d %02d:%02d', $y, $m, $d, $h, $mi ); + + my $lastCheck = ReadingsVal( $name, "lastCheck", "2000-01-01 00:00" ); + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "lastTTUpdate", $udate ); + readingsBulkUpdate( $hash, "lastCheck", $today ); + readingsBulkUpdate( $hash, ".overview", $res ); + readingsEndUpdate( $hash, 1 ); + + if ( time_str2num( $udate . ":00" ) < time_str2num( $lastCheck . ":00" ) ) { + Log3 $name, 4, "[$name] Last update from $udate, now it's $today. Quitting."; + return undef unless $hash->{helper}{forceRead}; + } + delete $hash->{helper}{forceRead}; + + my $i = 0; + CommandDeleteReading( undef, $name . " i.*" ); + readingsBeginUpdate($hash); + foreach my $line (@aus) { + my $reading = "i" . $i . "_"; + my %line = %{$line}; + foreach my $key ( keys %line ) { + my $val = %$line{$key}; + $val = "-" if ( !defined $val ); + readingsBulkUpdate( $hash, $reading . $key, $val ); + } + $i++; + } + readingsBulkUpdate( $hash, ".lastAResult", encode_json( \@aus ) ); + readingsEndUpdate( $hash, 1 ); + + Log3 $name, 4, "[$name] Extracted the url: " . $url; + + my $nparam = { + url => $url, + method => "GET", + hash => $hash, + callback => \&DSBMobile_getTTCallback + }; + Log3 $name, 5, "[$name] 2nd nonblocking HTTP Call starting"; + HttpUtils_NonblockingGet($nparam); + + return undef; + +} +##################################### +sub DSBMobile_getTTCallback($) { + my ( $param, $err, $data ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + Log3 $name, 5, "[$name] 2nd nonblocking HTTP Call returning"; + Log3 $name, 5, "[$name] Received HTML: " . $data; + + if ( $err ne "" ) { + Log3 $name, 3, "[$name] Error while requesting " . $param->{url} . " - $err"; + readingsSingleUpdate( $hash, "error", $err, 0 ); + return undef; + } + + $data =~ s/ /-/g; + $data = latin1ToUtf8($data); + Log3 $name, 4, "[$name] Starting extraction mon_list"; + + # Extract the tables + my $tdata = $data; + my @tt = $tdata =~ m/((?:(?:\r\n|[\r\n])[^\r\n]+?)*\/table>)/g; + + #my $te = HTML::TableExtract->new( attribs => { class => 'mon_list' } ); + #$te->parse($data); + my @tabs; + foreach my $ttab (@tt) { + my $te = HTML::TableExtract->new(); + $te->parse($ttab); + push( @tabs, $te->tables() ); + } + + Log3 $name, 4, "[$name] Starting extraction info"; + + # Extract the Info of the day + my $idata = $data; + my @dinfo = $idata =~ m/(
(?:(?:\r\n|[\r\n])[^\r\n]+?)*\/table>)/g; + + my @itabs; + foreach my $din (@dinfo) { + Log3 $name, 5, "[$name] Found info of the Day: " . Dumper($din); + my $tinfo = HTML::TableExtract->new(); + $tinfo->parse($din); + push( @itabs, $tinfo->tables() ); + } + + Log3 $name, 4, "[$name] Starting extraction mon_title"; + + #my $tree = HTML::TreeBuilder->new(); + #$tree->parse($data); + #$tree->eof(); + + # Let's find the dates first + #my @e = $tree->look_down( 'class' => 'mon_title' ); + + my @e = $data =~ m/class=\"mon_title\">(.*)<\/div>/g; + + my @result; + my @iresult; + + my $i = 0; + + my $class = AttrVal( $name, "dsb_classReading", "Klasse_n_" ); + my $filter = AttrVal( $name, "dsb_class", ".*" ); + my @ch; + + #foreach my $c (@e) { + foreach my $cn (@e) { + Log3 $name, 5, "[$name] Processing line #$i"; + + #Extract the date + #my $cn = $c->content(); + #my ( $date, undef ) = split( " ", @$cn[0] ); + my ( $date, undef ) = split( " ", $cn ); + my ( $d, $m, $y ) = split( /\./, $date ); + my $fdate = $y . "-" . sprintf( "%02s", $m ) . "-" . sprintf( "%02s", $d ); + + my $t = $tabs[$i]; + my $info = $itabs[$i]; + $i++; + my $j = 0; + + if ($t) { + foreach my $r ( $t->rows() ) { + my @f = @$r; + + #create readingnames from first line + if ( $j == 0 ) { + $j++; + @ch = map { makeReadingName($_) } @f; + next; + } + my %tst = (); + my $k = 0; + foreach my $cl (@ch) { + $tst{$cl} = $f[$k]; + $k++; + } + $tst{sdate} = $fdate; + + # my %row = ( + # sdate => $fdate, + # class => $f[0], + # hour => $f[1], + # teacherNew => $f[2], + # topicNew => $f[3], + # teacherOld => $f[4], + # roomNew => $f[5], + # topicOld => $f[6], + # substof => $f[7], + # substto => $f[8], + # roomOld => $f[9], + # comment => $f[10] + # ); + + #push( @result, \%roq ) if ( defined( $row{class} ) && $row{class} =~ /$filter/ ); + push( @result, \%tst ) + if ( defined( $tst{$class} ) + && $tst{$class} =~ /$filter/ ); + } + } + $j = 0; + + if ($info) { + foreach my $inf ( $info->rows() ) { + + #skip first line + if ( $j == 0 ) { + $j++; + next; + } + my @f = @$inf; + + my %irow = ( + sdate => $fdate, + topic => $f[0], + text => $f[1] + ); + push( @iresult, \%irow ); + } + } + } + + Log3 $name, 5, "[$name] Extracted Lines " . Dumper(@result); + + CommandDeleteReading( undef, $name . " tt.*" ); + CommandDeleteReading( undef, $name . " ti.*" ); + + $i = 0; + readingsBeginUpdate($hash); + foreach my $line (@result) { + my $reading = "tt" . $i . "_"; + my %line = %{$line}; + foreach my $key ( keys %line ) { + my $val = %$line{$key}; + $val = "-" if ( !defined $val ); + readingsBulkUpdate( $hash, $reading . $key, $val ); + } + $i++; + } + $i = 0; + foreach my $line (@iresult) { + my $reading = "ti" . $i . "_"; + my %line = %{$line}; + foreach my $key ( keys %line ) { + readingsBulkUpdate( $hash, $reading . $key, %$line{$key} ); + } + $i++; + } + + my ( $sec, $min, $hour, $mday, $month, $year, $wday, $yday, $isdst ) + = localtime( gettimeofday() ); + $month++; + $year += 1900; + my $today = sprintf( '%04d-%02d-%02d %02d:%02d', $year, $month, $mday, $hour, $min ); + + readingsBulkUpdate( $hash, ".lastResult", encode_json( \@result ) ); + readingsBulkUpdate( $hash, ".lastIResult", encode_json( \@iresult ) ); + readingsBulkUpdate( $hash, ".html", $data ); + readingsBulkUpdate( $hash, "state", "ok" ); + readingsBulkUpdate( $hash, "lastSync", $today ); + readingsBulkUpdate( $hash, "columnNames", join( ",", @ch ) ); + readingsEndUpdate( $hash, 1 ); + Log3 $name, 5, "[$name] 2nd nonblocking HTTP Call parse done"; + return undef; +} + +##################################### +sub DSBMobile_ProcessTimer($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + DSBMobile_query($hash); + + my $now = int( gettimeofday() ); + my $interval = AttrNum( $name, "dsb_interval", 0 ); + if ( $interval > 0 ) { + my $next = $now + $interval; + InternalTimer( $next, 'DSBMobile_ProcessTimer', $hash, 0 ); + } + +} +################################### +sub DSBMobile_Attr($) { + + my ( $cmd, $name, $aName, $aVal ) = @_; + my $hash = $defs{$name}; + if ( $cmd eq "set" ) { + if ( $aName eq "dsb_interval" ) { + if ( int( $aVal > 0 ) ) { + my $next = int( gettimeofday() ) + int($aVal); + InternalTimer( $next, 'DSBMobile_ProcessTimer', $hash, 0 ); + } + else { + RemoveInternalTimer($hash); + } + } + if ( $aName eq "dsb_class" or $aName eq "dsb_classReading" ) { + $hash->{helper}{forceRead} = 1; + } + } + elsif ( $cmd eq "del" ) { + if ( $aName eq "dsb_interval" ) { + RemoveInternalTimer($hash); + } + } + return undef; +} +################################### +sub DSBMobile_simpleHTML($;$) { + my ( $name, $infoDay ) = @_; + my $dat = ReadingsVal( $name, ".lastResult", "" ); + my $idat = ReadingsVal( $name, ".lastIResult", "" ); + my @cn = split( ",", ReadingsVal( $name, "columnNames", "" ) ); + my $classr = ReadingsVal( $name, "dsb_classReading", "Klasse_n_" ); + my $ret = "
"; + + $dat = decode_json($dat); + $idat = decode_json($idat); + + my @data = @$dat; + my @idata = @$idat; + return "no data found" if ( @data == 0 && ( @idata == 0 || !$infoDay ) ); + + if ( @data > 0 ) { + @data = sort { $a->{sdate} cmp $b->{sdate} or $a->{$classr} cmp $b->{$classr} } @data; + } + + # get today + my ( $sec, $min, $hour, $mday, $month, $year, $wday, $yday, $isdst ) + = localtime( gettimeofday() ); + $month++; + $year += 1900; + my $today = sprintf( '%04d-%02d-%02d', $year, $month, $mday ); + + # build a sorted array of valid dates + my %hash = (); + foreach my $d (@data) { + $hash{ $d->{sdate} } = 1 if $d->{sdate} ge $today; + } + if ($infoDay) { + foreach my $d (@idata) { + $hash{ $d->{sdate} } = 1 if $d->{sdate} ge $today; + } + } + my @days = keys %hash; + @days = sort { $a cmp $b } @days; + + my $date = ""; + my $class; + my $out = AttrVal( $name, "dsb_outputFormat", undef ); + + foreach my $day (@days) { + $ret .= "
"; + if ($infoDay) { + foreach my $iline (@idata) { + if ( $iline->{sdate} eq $day ) { + $ret .= ""; + } + } + } + foreach my $line (@data) { + if ( $line->{sdate} eq $day ) { + $ret .= ""; + + } + } + } + + # foreach my $line (@data) { + # next if ( $line->{sdate} lt $today ); + # if ( $line->{sdate} ne $date ) { + # $date = $line->{sdate}; + # $ret .= "
" . $day . "
" . $iline->{topic} . ": " . $iline->{text} . "
"; + if ($out) { + my $rep = $out; + foreach my $c (@cn) { + $rep =~ s/\%$c\%/$line->{$c}/g; + } + $ret .= $rep; + } + else { + foreach my $c (@cn) { + $ret .= $line->{$c} . " "; + } + } + $ret .= "
"; + # if ($infoDay) { + # foreach my $iline (@idata) { + # if ( $iline->{sdate} eq $date ) { + # $ret .= ""; + # } + # } + # } + # } + # $ret .= ""; + # } + $ret .= "
" . $line->{sdate} . "
" . $iline->{topic} . ": " . $iline->{text} . "
"; + # my $out = AttrVal( $name, "dsb_outputFormat", undef ); + # if ($out) { + # foreach my $c (@cn) { + # $out =~ s/\%$c\%/$line->{$c}/g; + # } + # $ret .= $out; + # } + # else { + # foreach my $c (@cn) { + # $ret .= $line->{$c} . " "; + # } + # } + # $ret .= "
"; + return $ret; + +} +################################### +sub DSBMobile_infoHTML($) { + my ( $name, $infoDay ) = @_; + my $dat = ReadingsVal( $name, ".lastAResult", "" ); + my $ret = ""; + + $dat = decode_json($dat); + + my @data = @$dat; + + @data = sort { $a->{date} cmp $b->{date} } @data; + + foreach my $line (@data) { + $ret .= ""; + } + $ret .= "
" + . "$line->{date}:$line->{title}
"; + return $ret; + +} +################################### +sub DSBMobileUUID() { + my @chars = ( 'a' .. 'f', 0 .. 9 ); + my @string; + push( @string, $chars[ int( rand(16) ) ] ) for ( 1 .. 32 ); + splice( @string, 8, 0, '-' ); + splice( @string, 13, 0, '-' ); + splice( @string, 18, 0, '-' ); + splice( @string, 23, 0, '-' ); + return join( '', @string ); +} + +1; + +=pod +=item helper +=item summary Gets timetable information from DSBMobile +=item summary_DE Liest Vertretungspläne von DSBMobile + + +=begin html + + +
+
    + DSBMobile reads and displays timetable change information from DSBMobile App, which is used at some schools in Germany (at least)

    + + Define +
      + DSBMobile uses several perl modules which have to be installed in advance: +
        +
      • IO::Compress::Gzip
      • +
      • IO::Uncompress::Gunzip
      • +
      • MIME::Base64
      • +
      • HTML::TableExtract
      • + DSBMobile will be defined without Parameters. +

        + define <devicename> DSBMobile

        +

        +
      +
    + + Get + + + Readings +
      + Following readings are created: +
        +
      • columnNames: Readingnames generated dynamically from substitution table column headers
      • +
      • lastCheck: Date/Time of the last successful check for new data
      • +
      • lastSync: Date/Time of the last run where data was actually synchronized (not only checked)
      • +
      • lastTTUpdate: Date/Time of the last update of the timetable data on the DSBMobile server
      • +
      • error: contains the error message of the last error that occured while fetching data
      • +
      • state: "ok" if last run was successful, "error" if not.
      • +
      + For each posting and change in the timetable the following readings are generated +
        +
      • i#_date: Date of the posting
      • +
      • i#_title: Title of the posting
      • +
      • i#_url: Link to the posting
      • +
      • ti#_sdate: Date of the "Info of the day"
      • +
      • ti#_topic: Title of the "Info of the day"
      • +
      • ti#_text: Content of the "Info of the day"
      • +
      • tt#_xxxx: Dynamically generated reading for each column of the substitution table
      • +
      +
    + + Attributes +
      +
        +
      • dsb_user: The user to log in to DSBMobile
      • +
      • dsb_password: The password to log in to DSBMobile
      • +
      • dsb_class: The grade to filter for. Can be a regex, e.g. 5a|8b or 6.*c.
      • +
      • dsb_classReading: Has to be set if the column containing the class(es) is not named "Klasse(n)", i.e. the genarated reading is not "Klasse_n_"
      • +
      • dsb_interval: Interval in seconds to pull DSBMobile, value of 0 means disabled
      • +
      • dsb_outputFormat: can be used to format the output of the weblink. Takes the readingnames enclosed in % as variables, e.g. %Klasse_n_%
      • +
      +
    + DSBMobile additionally provides two functions to display the information in weblinks: +
      +
    • DSBMobile_simpleHTML($name ["dsb","showInfoOfTheDay"]): Shows the timetable changes, if the second optional parameter is "1", the Info of the Day will be displayed additionally. + Example defmod dsb_web weblink htmlCode {DSBMobile_simpleHTML("dsb",1)} +
    • +
    • DSBMobile_infoHTML($name): Shows the postings with links to the Details. + Example defmod dsb_infoweb weblink htmlCode {DSBMobile_infoHTML("dsb")} +
    • +
    +
+
+ +=end html +=begin html_DE + + +
+
    + DSBMobile liest die Vertretungspläne der DSBMobile App, die (zumindest) an einigen Schulen in Deutschland verwendet wird

    + +
+ Define +
    + DSBMobile verwendet einige Perl-Module, die vorab installiert werden müssen: +
      +
    • IO::Compress::Gzip
    • +
    • IO::Uncompress::Gunzip
    • +
    • MIME::Base64
    • +
    • HTML::TableExtract
    • + DSBMobile wird ohne Parameter definiert. +

      + define <devicename> DSBMobile

      +

      +
    + + Get + + + Readings +
      + Die folgenden Readings werden erstellt: +
        +
      • columnNames: Readingnamen, die dynamisch aus den Spaltenüberschriften des Vertretungsplans generiert werden
      • +
      • lastCheck: Datum/Uhrzeit der letzten erfolgreichen Überprüfung auf neue Daten
      • +
      • lastSync: Datum/Uhrzeit der letzten erfolgreichen Synchronisierung neuer Daten(nicht nur Überprüfung)
      • +
      • lastTTUpdate: Datum/Uhrzeit der letztenAktualisierung auf dem DSBMobile Server
      • +
      • error: enthält die letzte Fehlermeldung, die bei der Datensynchronisierung aufgetreten ist
      • +
      • state: "ok" sofern der letzte Abruf erfolgreich war, "error" wenn nicht.
      • +
      + Für jeden Aushang bzw. jede Vertretung werden folgende Readings erstellt +
        +
      • i#_date: Datum des Aushangs
      • +
      • i#_title: Titel des Aushangs
      • +
      • i#_url: Link zum Aushang
      • +
      • ti#_sdate: Datum der "Info des Tages"
      • +
      • ti#_topic: Titel der "Info des Tages"
      • +
      • ti#_text: Inhalt der "Info des Tages"
      • +
      • tt#_xxxxDynamisch generierte Readings für jede Spalte des Vertretungsplanes
      • +
      +
    + + Attributes +
      +
        +
      • dsb_user: Der User für die DSBMobile-Anmeldung
      • +
      • dsb_password: Das Passwort für die DSBMobile-Anmeldung
      • +
      • dsb_class: Die Klasse nach der gefiltert werden soll. Kann eine Regex sein, z.B. 5a|8b or 6.*c.
      • +
      • dsb_classReading: Muss gesetzt werden, wenn die Spalte mit der Klasse nicht "Klasse(n)" heisst, d.h. das generierte reading nicht "Klasse_n_" lautet
      • +
      • dsb_interval: Intervall in Sekunden in dem Daten von DSBMobile abgerufen werden, ein Wert von 0 bedeuted disabled
      • +
      • dsb_outputFormat: Kann benutzt werden, um den Output des weblinks zu formatieren. Die Readingnamen von % umschlossen können als Variablen verwendet werden, z.B. %Klasse_n_%
      • +
      +
    + DSBMobile bietet zusätzlich zwei Funktionen, um die Informationen in weblinks darzustellen: +
      +
    • DSBMobile_simpleHTML($name ["dsb",showInfoOfTheDay]): Zeigt den Vertretungsplan, wenn der zweite optionale Parameter auf "1" gesetzt wird, wird die Info des Tages zusätzlich mit angezeigt. + Beispiel defmod dsb_web weblink htmlCode {DSBMobile_simpleHTML("dsb",1)} +
    • +
    • DSBMobile_infoHTML($name): Zeigt die Aushänge mit Links zu den Details. + Beispiel defmod dsb_infoweb weblink htmlCode {DSBMobile_infoHTML("dsb")} +
    • +
    +
+
+=end html_DE + +=for :application/json;q=META.json 98_DSBMobile.pm + { + "abstract": "Gets timetable information from DSBMobile(JSON)", + "description": "DSBMobile reads and displays timetable change information from DSBMobile App, which is used at some schools in Germany (at least).", + "x_lang": { + "de": { + "abstract": "Liest Vertretungspläne von DSBMobile", + "description": "DSBMobile liest Vertretungspläne von DSBMobile und zeigt sie an. DSBMobile wird (zumindest) an einigen Schulen in Deutschland verwendet" + } + }, + "license":"gpl_2", + "version": "v0.0.4", + "x_release_date": "2020-10-01", + "release_status": "testing", + "author": [ + "Oli Merten aka KernSani" + ], + "x_fhem_maintainer": [ + "KernSani" + ], + "keywords": [ + "DSBMobile", + "Schule", + "Vertretungsplan", + "Vertretung", + "Stundenplan", + "school", + "timetable", + "substitutions" + ], + "prereqs": { + "runtime": { + "requires": { + "FHEM": 5.00918623, + "FHEM::Meta": 0.001006, + "HttpUtils": 0, + "JSON": 0, + "perl": 5.014, + "IO::Compress::Gzip": 0, + "IO::Uncompress::Gunzip": 0, + "MIME::Base64": 0, + "HTML::TableExtract": 0, + "HTML::TreeBuilder": 0 + }, + "recommends": { + }, + "suggests": { + } + } + }, + "x_copyright": { + "mailto": "oli.merten@gmail.com", + "title": "Oli Merten" + }, + "x_support_community": { + "title": "Support Thread", + "web": "https://forum.fhem.de/index.php/topic,107104.msg1011580.html" + }, + "x_support_status": "supported" + } + +=end :application/json;q=META.json + +=cut