diff options
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/efa-m | 778 |
1 files changed, 711 insertions, 67 deletions
@@ -2,66 +2,292 @@ use strict; use warnings; use 5.010; +use utf8; -our $VERSION = '1.01'; +our $VERSION = '3.15'; binmode( STDOUT, ':encoding(utf-8)' ); -use Getopt::Long qw(:config no_ignore_case); -use List::Util qw(max); -use Travel::Status::DE::VRR; +use Encode qw(decode); +use Getopt::Long qw(:config no_ignore_case bundling); +use JSON; +use List::Util qw(first max none); +use Travel::Status::DE::EFA; + +my $service = 'VRR'; +my $efa_url; +my $efa_encoding; +my $use_cache = 1; +my $cache; +my ( $json_output, $raw_json_output ); +my ( $date, $time, $input_type, $list_lines, $offset, $relative_times ); +my ( $full_routes, $filter_via, $show_jid ); +my ( $timeout, $developer_mode ); +my ( @grep_lines, @grep_platforms, @grep_mots ); +my ( %edata, @edata_pre ); +my ( $list_services, $discover_and_print, $discover ); +my $efa; + +my %occupancy_map = ( + MANY_SEATS => '.', + FEW_SEATS => 'o', + STANDING_ONLY => '*', + FULL => '!', + unknown => '?', +); -my ( $date, $time, $input_type, $list_lines ); -my ( @grep_lines, @grep_platforms ); +@ARGV = map { decode( 'UTF-8', $_ ) } @ARGV; GetOptions( - 'd|date=s' => \$date, - 'h|help' => sub { show_help(0) }, - 'l|line=s@' => \@grep_lines, - 'L|linelist' => \$list_lines, - 'p|platform=s@' => \@grep_platforms, - 't|time=s' => \$time, - 'V|version' => \&show_version, + 'A|auto-url|discover-and-print' => \$discover_and_print, + 'd|date=s' => \$date, + 'D|discover' => \$discover, + 'h|help' => sub { show_help(0) }, + 'j|with-jid' => \$show_jid, + 'l|line=s@' => \@grep_lines, + 'L|linelist' => \$list_lines, + 'list' => \$list_services, + 'm|mot=s@' => \@grep_mots, + 'o|offset=i' => \$offset, + 'O|output=s@' => \@edata_pre, + 'p|platform=s@' => \@grep_platforms, + 'r|relative' => \$relative_times, + 's|service=s' => \$service, + 't|time=s' => \$time, + 'timeout=i' => \$timeout, + 'u|efa-url=s' => \$efa_url, + 'v|via=s' => \$filter_via, + 'V|track-via=s' => \$filter_via, + 'cache!' => \$use_cache, + 'json' => \$json_output, + 'raw-json' => \$raw_json_output, + 'devmode' => \$developer_mode, + 'version' => \&show_version, ) or show_help(1); -if ( @ARGV != 2 ) { +if ($list_services) { + show_services(); +} + +if ( @ARGV < 1 or @ARGV > 2 ) { show_help(1); } +if ($use_cache) { + my $cache_path = ( $ENV{XDG_CACHE_HOME} // "$ENV{HOME}/.cache" ) + . '/Travel-Status-DE-EFA'; + eval { + require Cache::File; + $cache = Cache::File->new( + cache_root => $cache_path, + default_expires => '90 seconds', + lock_level => Cache::File::LOCK_LOCAL(), + ); + }; + if ($@) { + $cache = undef; + } +} + # --line=foo,bar support +@edata_pre = split( qr{,}, join( q{,}, @edata_pre ) ); @grep_lines = split( qr{,}, join( q{,}, @grep_lines ) ); +@grep_mots = split( qr{,}, join( q{,}, @grep_mots ) ); @grep_platforms = split( qr{,}, join( q{,}, @grep_platforms ) ); -my ( $place, $input ) = @ARGV; +my ( $place, $input, $coord, $stopseq, $stopfinder ); + +if ( @ARGV == 1 ) { + if ( $ARGV[0] + =~ m{ ^ ([^@]*) @ ([^@]*) [(] ([^T]*) T ([^)]*) [)] (.*) $ }x ) + { + $stopseq = { + stateless => $1, + stop_id => $2, + date => $3, + time => $4, + key => $5 + }; + } + elsif ( $ARGV[0] =~ m{ ^ [?] (?<name> .*) $ }x ) { + $stopfinder = { + name => $+{name}, + }; + } + elsif ( $ARGV[0] =~ m{ ^ (?<lat> [0-9.]+ ) : (?<lon> [0-9].+ ) $ }x ) { + $coord = { + lat => $+{lat}, + lon => $+{lon}, + }; + } + else { + $input = $ARGV[0]; + } +} +else { + ( $place, $input ) = @ARGV; +} -if ( $input =~ s{ ^ (?<type> address|poi|stop) : }{}x ) { +if ( $input and $input =~ s{ ^ (?<type> address|poi|stop|stopID) : }{}x ) { $input_type = $+{type}; } -my $status = Travel::Status::DE::VRR->new( - date => $date, - place => $place, - name => $input, - time => $time, - type => $input_type, -); +for my $efield (@edata_pre) { + if ( $efield eq 'a' ) { $edata{route_after} = 1; $full_routes = 1 } + elsif ( $efield eq 'b' ) { $edata{route_before} = 1; $full_routes = 1 } + elsif ( $efield eq 'f' ) { $edata{fullroute} = 1; $full_routes = 1 } + elsif ( $efield eq 'r' ) { $edata{route} = 1; $full_routes = 1 } + elsif ( $efield eq 'i' ) { $edata{info} = 1 } + elsif ( $efield eq 'm' ) { $edata{messages} = 1 } + else { $edata{$efield} = 1 } +} +if ($filter_via) { + $full_routes = 1; +} + +if ($efa_url) { + $service = undef; +} +elsif ($service) { + my $service_ref = Travel::Status::DE::EFA::get_service($service); + if ( not $service_ref ) { + printf STDERR ( + "Error: Unknown service '%s'. See 'efa-m --list' for a " + . "list of supported service names\n", + $service + ); + exit 1; + } + $efa_encoding = $service_ref->{encoding}; + $efa_url = undef; +} + +sub new_efa { + my ( $s, $u ) = @_; + my $res = Travel::Status::DE::EFA->new( + service => $s, + efa_url => $u, + cache => $cache, + date => $date, + developer_mode => $developer_mode, + efa_encoding => $efa_encoding, + full_routes => $full_routes, + place => $place, + name => $input, + coord => $coord, + stopfinder => $stopfinder, + stopseq => $stopseq, + time => $time, + type => $input_type, + timeout => $timeout, + ); + + return $res; +} sub show_help { my ($code) = @_; - print "Usage: efa-m [-d <dd.mm.yyyy>] [-t <hh:mm>] <city> <station>\n" + print "Usage: efa-m [-d <dd.mm.yyyy>] [-t <hh:mm>] <stop>\n" . "See also: man efa-m\n"; exit $code; } +sub show_services { + printf( "%-45s %-14s %s\n\n", 'service', 'abbr. (-s)', 'url (-u)' ); + for my $shortname ( Travel::Status::DE::EFA::get_service_ids() ) { + my $service = Travel::Status::DE::EFA::get_service($shortname); + printf( "%-45s %-14s %s\n", + $service->{name}, $shortname, $service->{url} ); + } + + exit 0; +} + sub show_version { say "efa-m version ${VERSION}"; exit 0; } +sub format_delay { + my ( $delay, $len ) = @_; + if ( $delay and $len ) { + return sprintf( "(%+${len}d)", $delay ); + } + return q{}; +} + +sub format_route { + my (@route) = @_; + + my $output = q{}; + + for my $stop (@route) { + if ( not $stop ) { + say 'BUG'; + next; + } + my $occupancy + = $stop->occupancy ? format_occupancy( $stop->occupancy ) : q{ }; + my $delay = q{ }; + if ( $stop->delay ) { + $delay = sprintf( '(%+3d)', $stop->delay ); + } + if ( $stop->is_cancelled ) { + $output .= sprintf( + " --:-- %s %s %35s %s\n", + $delay, $occupancy, $stop->full_name, $stop->platform // q{}, + ); + } + elsif ( defined $stop->arr and defined $stop->dep ) { + if ( $stop->arr->epoch == $stop->dep->epoch ) { + $output .= sprintf( + " %5s %s %s %35s %s\n", + $stop->arr->strftime('%H:%M'), + $delay, $occupancy, $stop->full_name, + $stop->platform // q{}, + ); + } + else { + $output .= sprintf( + "%5s → %5s %s %s %35s %s\n", + $stop->arr->strftime('%H:%M'), + $stop->dep->strftime('%H:%M'), + $delay, + $occupancy, + $stop->full_name, + $stop->platform // q{}, + ); + } + } + elsif ( defined $stop->arr ) { + $output .= sprintf( + "%5s %s %s %35s %s\n", + $stop->arr->strftime('%H:%M'), + $delay, $occupancy, $stop->full_name, $stop->platform, + ); + } + elsif ( defined $stop->dep ) { + $output .= sprintf( + " %5s %s %s %35s %s\n", + $stop->dep->strftime('%H:%M'), + $delay, $occupancy, $stop->full_name, $stop->platform, + ); + } + elsif ( $stop->full_name ) { + $output .= sprintf( " %s %s %35s %s\n", + $delay, $occupancy, $stop->full_name, $stop->platform, ); + } + else { + $output .= "?\n"; + } + } + return $output; +} + sub display_result { my (@lines) = @_; @@ -71,41 +297,125 @@ sub display_result { die("Nothing to show\n"); } - for my $i ( 0 .. 3 ) { + for my $i ( 0 .. 4 ) { $line_length[$i] = max map { length( $_->[$i] ) } @lines; } for my $line (@lines) { - if ( length( $line->[4] ) ) { - $line->[4] =~ tr{\n\x0d}{ }s; - chomp $line->[4]; + if ( $edata{messages} and $line->[5]->hints ) { print "\n"; - for my $info_line ( split( qr{\n}, $line->[4] ) ) { - say "# ${info_line}"; + for my $hint ( $line->[5]->hints ) { + $hint =~ tr{\n\x0d}{ }s; + chomp $hint; + say "# ${hint}"; } } printf( join( q{ }, ( map { "%-${_}s" } @line_length ) ) . "\n", - @{$line}[ 0 .. 3 ] + @{$line}[ 0 .. 4 ] ); + + if ( $line->[6] ) { + say $line->[6]; + } } return; } +sub show_coord { + my $max_len = max map { length( $_->full_name ) } $efa->results; + for my $stop ( $efa->results ) { + printf( + "%5.1f km %-${max_len}s %s\n", + $stop->distance_m * 1e-3, + $stop->full_name, $stop->id_code + ); + } +} + +sub show_stopfinder { + my $max_len = max map { length( $_->full_name ) } $efa->results; + for my $stop ( $efa->results ) { + printf( "%-${max_len}s %s %s\n", + $stop->full_name, $stop->id_num, $stop->id_code ); + } +} + +sub show_stopseq { + my $trip = $efa->result; + + printf( "%s %s → %s\n", $trip->type, $trip->line // q{}, $trip->dest_name ); + + printf( + "Fahrt %s am %s\n", + $trip->number || $stopseq->{stateless}, + ( $trip->route )[0]->sched_dep->strftime('%d.%m.%Y'), + ); + say q{}; + + my $occupancy_len = 0; + my $delay_len = 0; + my $inner_delay_len = 0; + my $max_delay = max map { abs( $_->delay // 0 ) } $trip->route; + if ($max_delay) { + $inner_delay_len = length($max_delay) + 1; + $delay_len = length( sprintf( '(%+d)', $max_delay ) ) + 1; + } + if ( first { $_->occupancy } $trip->route ) { + $occupancy_len = 2; + } + + if ( first { $_->is_cancelled } $trip->route and $delay_len < 3 ) { + $delay_len = 3; + } + + for my $stop ( $trip->route ) { + printf( + "%s → %s%${delay_len}s %-${occupancy_len}s%s (%s) %s\n", + $stop->arr ? $stop->arr->strftime('%H:%M') + : q{ }, + $stop->dep ? $stop->dep->strftime('%H:%M') + : q{ }, + $stop->is_cancelled ? 'XX' + : ( + $stop->delay + ? sprintf( " (%+${inner_delay_len}d)", $stop->delay ) + : q{} + ), + $stop->occupancy ? format_occupancy( $stop->occupancy ) : q{}, + $stop->full_name, + $stop->niveau // q{?}, + $stop->platform // q{} + ); + } +} + sub show_lines { my @output; - for my $l ( $status->lines ) { + for my $l ( $efa->lines ) { - if ( @grep_lines and not( $l->name ~~ \@grep_lines ) ) { + if ( ( @grep_lines and none { $l->number eq $_ } @grep_lines ) + or ( @grep_mots and none { $l->mot_name eq $_ } @grep_mots ) ) + { next; } - push( @output, - [ $l->type, $l->name, $l->direction // q{}, $l->route // q{} ] ); + if ( @grep_mots and none { $l->mot_name eq $_ } @grep_mots ) { + next; + } + + push( + @output, + [ + $l->type, $l->number, + $l->direction // q{}, q{}, + $l->route // q{} + ] + ); } display_result(@output); @@ -113,51 +423,195 @@ sub show_lines { return; } +sub format_occupancy { + my ($occupancy) = @_; + + return $occupancy_map{$occupancy} // $occupancy_map{unknown}; +} + sub show_results { my @output; - for my $d ( $status->results ) { - - my $platform = $d->platform; - my $dtime = $d->time; + my $delay_len = 0; + my $delay_fmt = 0; + for my $d ( $efa->results ) { + if ( $d->delay ) { + $delay_len = max( $delay_len, length( $d->delay ) + 1 ); + } + } + if ($delay_len) { + $delay_fmt = $delay_len + 3; + } - if ( $d->platform_db ) { - $platform .= ' (DB)'; + if ( scalar $efa->stops > 1 ) { + for my $stop ( $efa->stops ) { + say $stop->full_name; } + } + elsif ( $efa->stop->full_name ) { + say $efa->stop->full_name; + } + + for my $d ( $efa->results ) { + + my @output_line; + my $platform = $d->platform; + my $dtime = ( + $relative_times + ? sprintf( '%2d min', $d->countdown ) + : $d->datetime->strftime('%H:%M') + ); if ( - ( @grep_lines and not( $d->line ~~ \@grep_lines ) ) + ( @grep_lines and none { $d->line eq $_ } @grep_lines ) + or ( @grep_mots and none { $d->mot_name eq $_ } @grep_mots ) or ( @grep_platforms - and not( $platform ~~ \@grep_platforms ) ) + and none { $platform eq $_ } @grep_platforms ) + or ( $offset and $d->countdown < $offset ) + or ( $filter_via + and + not( first { $_->name =~ m{$filter_via}io } $d->route_post ) ) ) { next; } - if ( $d->delay ) { - if ($d->delay eq '-9999') { - $dtime .= ' CANCELED'; + if ( $d->is_cancelled ) { + if ($relative_times) { + next; } else { - $dtime .= ' (+' . $d->delay . ')'; + $dtime = '--:--'; } } + elsif ($filter_via) { + my $via = first { $_->name =~ m{$filter_via}io } $d->route_post; + $dtime + .= ' → ' + . $via->arr->clone->add( minutes => $d->delay // 0 ) + ->strftime('%H:%M'); + } + if ( $d->delay ) { + $dtime .= ' ' . format_delay( $d->delay, $delay_len ); + } + + my $line = $d->line; + if ( ( length($line) > 10 or not $line ) + and $d->train_type + and $d->train_no ) + { + $line = $d->train_type . ' ' . $d->train_no; + } + + @output_line = ( $dtime, $platform, $line, q{}, $d->destination, $d ); - push( @output, - [ $dtime, $platform, $d->line, $d->destination, $d->info ] ); + if ($show_jid) { + $output_line[2] .= ' ' . $d->id; + } + + if ( $edata{route} ) { + $output_line[3] + = join( q{ }, map { $_->name } $d->route_interesting ); + } + elsif ( $d->occupancy ) { + $output_line[3] = format_occupancy( $d->occupancy ); + } + + if ( $edata{fullroute} ) { + $output_line[6] + = format_route( $d->route_pre ) + . ' -' x 30 . "\n" + . format_route( $d->route_post ); + } + elsif ( $edata{route_after} ) { + $output_line[6] = format_route( $d->route_post ); + } + elsif ( $edata{route_before} ) { + $output_line[6] = format_route( reverse $d->route_pre ); + } + + push( @output, \@output_line ); } display_result(@output); + if ( $edata{info} ) { + for my $info ( $efa->infos ) { + say q{}; + if ( $info->subject and $info->subtitle ne $info->subject ) { + printf( "# %s\n%s\n", $info->subtitle, $info->subject ); + } + else { + printf( "# %s\n", $info->subtitle ); + } + } + } + return; } -if ( my $err = $status->errstr ) { +if ( $discover or $discover_and_print ) { + for my $shortname ( Travel::Status::DE::EFA::get_service_ids() ) { + $efa = new_efa($shortname); + if ( $efa and not $efa->errstr ) { + if ($discover_and_print) { + last; + } + my $service_ref = Travel::Status::DE::EFA::get_service($shortname); + printf( + "%s / %s (%s)\n -> efa-m -s %s %s\n\n", + $service_ref->{name}, $shortname, $service_ref->{url}, + $shortname, join( q{ }, map { "'$_'" } @ARGV ), + ); + } + } + if ($discover) { + exit 0; + } +} + +$efa = new_efa( $service, $efa_url ); + +if ( my $err = $efa->errstr ) { say STDERR "Request error: ${err}"; + + if ( $efa->place_candidates ) { + say 'You might want to try one of the following places:'; + for my $candidate ( $efa->place_candidates ) { + printf( "%d %s\n", $candidate->id_num, $candidate->name ); + } + } + elsif ( $efa->name_candidates ) { + say 'You might want to try one of the following names:'; + for my $candidate ( $efa->name_candidates ) { + printf( "%d %s\n", $candidate->id_num, $candidate->name ); + } + } + exit 2; } -if ($list_lines) { +if ($json_output) { + if ($stopseq) { + say JSON->new->canonical->convert_blessed->encode( $efa->result ); + } + else { + say JSON->new->canonical->convert_blessed->encode( [ $efa->results ] ); + } +} +elsif ($raw_json_output) { + say JSON->new->canonical->convert_blessed->encode( $efa->{response} ); +} +elsif ($coord) { + show_coord(); +} +elsif ($stopfinder) { + show_stopfinder(); +} +elsif ($stopseq) { + show_stopseq(); +} +elsif ($list_lines) { show_lines(); } else { @@ -168,47 +622,223 @@ __END__ =head1 NAME -efa-m - Unofficial interface to the efa.vrr.de departure monitor +efa-m - Unofficial interface to the efa.vrr.de departure and trip monitor =head1 SYNOPSIS -B<efa-m> [B<-d> I<date>] [B<-t> I<time>] I<city> [I<type>B<:>]I<name> +B<efa-m> [B<-jLr>] [B<-d> I<dd.mm.yyyy>] [B<-t> I<hh:mm>] +[B<-l> I<lines>] [B<-p> I<platforms>] [B<-s> I<service>] +[I<city>] [I<type>B<:>]I<name> + +B<efa-m> [B<-s> I<service>] B<?>I<query>|I<lat>B<:>I<lon> + +B<efa-m> [B<-s> I<service>] I<tripid> =head1 VERSION -version 1.01 +version 3.15 =head1 DESCRIPTION -B<efa-m> lists upcoming tram, bus and train departures at the location I<name> -in I<city>. +B<efa-m> is an interface to EFA public transport services. + +It can serve as a departure monitor, request details about a specific +trip/journey, and look up public transport stops by name or geolocation. +The operating mode depends on the contents of its mandatory argument. + +=head2 Departure Monitor (I<name>) + +Shows departures at I<name> or I<city> I<name>; I<name> may also be a stop ID +number or code. For each departure, B<efa-m> shows + +=over + +=item * estimated departure time (including delay, if available), + +=item * delay in minutes, + +=item * platform, + +=item * line, + +=item * expected occupation (range . o * !, if available), and + +=item * destination. + +=back + +If I<city> is specified, I<name> refers to a location within I<city>. +Otherwise, I<name> must be self-contained. I.e., both C<< efa-m Essen Hbf >> +and C<< efa-m "Essen Hbf" >> are valid. Note, however, than C<< efa-m E Hbf >> +works, but C<< efa-m "E Hbf" >> does not. By default, I<name> refers to a stop, this can be changed by specifying I<type>. Supported types are B<address> and B<poi> (point of interest). +=head2 Location Search (B<?>I<query>|I<lat>B<:>I<lon>) + +List stops that match I<query> or that are located in the vicinity of +I<lat>B<:>I<lon> geocoordinates. In addition to stop names, the output also +includes stop ID codes (both modes) and numbers (only available in I<query> +mode). + +=head2 Trip Details (I<JourneyID>) + +List trip information including arrival and departure time, name, and platform +of each stop on the trip's route. + =head1 OPTIONS +Values in brackets indicate options that only apply to the corresponding +operating mode(s). + =over -=item B<-d>, B<--date> I<dd.mm.yyyy> +=item B<-A>, B<--auto-url>, B<--discover-and-print> (monitor) + +Probe all known EFA services for the specified stop. Print the first result +which was not an error. -Show departures for I<date> instead of today +Note that this may take a while and will not necessarily return the best +result. Also, using thi option by default is not recommended, as it puts EFA +services under considerable additional load. -=item B<-l>, B<--line> I<lines> +=item B<-d>, B<--date> I<dd.mm.yyyy> (monitor) + +Show departures for I<date> instead of today. +May also be specified as I<dd.mm.> + +=item B<-D>, B<--discover> (monitor) + +Probe all known EFA services for the specified stop. Print the URLs and names +of all services which did not return an error. + +=item B<-j>, B<--with-jid> (monitor) + +Show journey ID for each departure. +The ID can be used to query details with a subsequent B<efa-m> invocation +(trip details mode). + +=item B<--json> + +Print result(s) as JSON and exit. This is a dump of internal data structures +and not guaranteed to remain stable between minor versions. Please use the +Travel::Status::DE::EFA(3pm) module if you need a proper API. + +=item B<-L>, B<--linelist> (monitor) + +Do not show departures. Instead, list all lines serving the specified place. +Note that this information may be incomplete -- only lines which are in +service either at the time of the B<efa-m> call or at the time specifed +using B<--date> and B<--time> are guaranteed to be included. + +=item B<-l>, B<--line> I<lines> (monitor) Only show departures of I<lines> (comma-separatad list, option may be repeated) -=item B<-p>, B<--platform> I<platforms> +=item B<--list> + +List supported EFA services with their URLs (see B<-u>) and abbreviations (see +B<-s>). + +=item B<-m>, B<--mot> I<motlist> (monitor) + +Only show departures whose type appears in I<motlist> (comma-separated list, +this option may be repeated). + +The following departure types ("modes of transport") are supported: +zug, s-bahn, u-bahn, stadtbahn, tram, stadtbus, regionalbus, schnellbus, +seilbahn, schiff, ast, sonstige + +=item B<-o>, B<--offset> I<minutes> (monitor) + +Ignore departures which are less than I<minutes> from now. + +=item B<-O>, B<--output> I<outputtypes> (monitor) + +For each result, show additional information as specified by I<outputtypes>. +I<outputtypes> is a comma-separated list, the B<-O>/B<--output> option may +also be repeated. Each output type has both a short and long form, for instance +both "-Or" and "--output=route" are valid. + +The following output types are supported: + +=over + +=item a / route_after + +Show each departure's full route (timestamps and stop names) after the +requested station. + +=item b / route_before + +Show each departure's full route (timestamps and stop names) before the +requested station. + +=item f / fullroute + +Show each departure's full route (timestamps and stop names) before and +after the requested station. + +=item i / info + +Show station-specific information messages. These typically relate to +construction work, broken elevators or escalators, or special announcements +for large-scale events. + +=item r / route + +Show up to three stops between the requested station and the departure's +destination. B<efa-m> tries to display the three most important stops, +however these are heuristically determined and may not be optimal. + +=item m / messages + +Show free-text messages associated with individual departures. These can +include generic information such is bicycle transportation options or Wi-Fi +availability, delay reasons, and more. + +=back + +=item B<-p>, B<--platform> I<platforms> (monitor) Only show departures at I<platforms> (comma-separated list, option may be repeated). Note that the C<< Bstg. >> / C<< Gleis >> prefix must be omitted. -=item B<-t>, B<--time> I<hh:mm> +=item B<--raw-json> + +Print unprocessed EFA response as JSON and exit. +Useful for debugging and development purposes. + +=item B<-r>, B<--relative> (monitor) + +Show relative departure times in minutes (i.e. the time difference between +the departure and the time of the request). In this case, realtime data is +already included. -Show departures starting at I<time> instead of now +=item B<-s>, B<--service> I<name> -=item B<-V>, B<--version> +Short name of the EFA service. See Travel::Status::DE::EFA(3pm) and the +B<--list> option for a list of services. + +=item B<-t>, B<--time> I<hh:mm> (monitor) + +Show departures starting at I<time> instead of now. + +=item B<--timeout> I<seconds> + +Set timeout for HTTP requests. Default: 10 seconds. Set to 0 or a negative +value to disable it. + +=item B<-v>, B<--via> I<station> (monitor) + +Only show trains serving I<station> after the requseted stop, and show the +arrival time at I<station> after the departure time at the current stop. +I<station> is matched against the "I<city> I<stop>" fields in each line's +route. Regular expressions are also supported. + +=item B<--version> Show version information. @@ -216,7 +846,8 @@ Show version information. =head1 EXIT STATUS -Zero. +Normally zero. B<1> means B<efa-m> was called with invalid options, +B<2> indicates a request error from Travel::Status::DE::EFA(3pm). =head1 CONFIGURATION @@ -230,17 +861,30 @@ None. =item * LWP::UserAgent(3pm) -=item * XML::LibXML(3pm) +=item * Travel::Status::DE::EFA(3pm) =back =head1 BUGS AND LIMITATIONS -Unknown. +B<efa-m> uses the VRR EFA service by default, which seems to contain the +greatest available set of information. However, some cities (e.g. Berlin or +parts of Hamburg) are not supported there, and B<efa-m> is not yet able to +choose the appropriate EFA URL for these by itself. In these cases, you should +find an appropriate EFA service using the B<-D>/B<--discover> option and then +use B<-s> I<service> when making requests. + +=over + +=item * EFA does not provide real-time data for the routes of requested +departures. Hence, B<--via> estimates the arrival time from scheduled +departure and departure delay + +=back =head1 AUTHOR -Copyright (C) 2011 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt> +Copyright (C) 2011-2023 Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt> =head1 LICENSE |