summaryrefslogtreecommitdiff
path: root/bin/efa-m
diff options
context:
space:
mode:
Diffstat (limited to 'bin/efa-m')
-rwxr-xr-xbin/efa-m587
1 files changed, 457 insertions, 130 deletions
diff --git a/bin/efa-m b/bin/efa-m
index 326d2cb..0f7fb44 100755
--- a/bin/efa-m
+++ b/bin/efa-m
@@ -4,28 +4,38 @@ use warnings;
use 5.010;
use utf8;
-no if $] >= 5.018, warnings => 'experimental::smartmatch';
-
-our $VERSION = '1.17';
+our $VERSION = '3.13';
binmode( STDOUT, ':encoding(utf-8)' );
-use Encode qw(decode);
+use Encode qw(decode);
use Getopt::Long qw(:config no_ignore_case bundling);
-use List::Util qw(first max);
+use JSON;
+use List::Util qw(first max none);
use Travel::Status::DE::EFA;
-my $efa_url = 'https://efa.vrr.de/vrr/XSLT_DM_REQUEST';
+my $service = 'VRR';
+my $efa_url;
my $efa_encoding;
-my ( $date, $time, $input_type, $list_lines, $offset, $relative_times );
-my ($full_routes);
-my ( $filter_via, $track_via );
-my ( $timeout, $developer_mode );
-my ( @grep_lines, @grep_platforms, @grep_mots );
-my ( %edata, @edata_pre );
-my ( $list_services, $service, $discover_and_print, $discover );
+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 => '?',
+);
+
@ARGV = map { decode( 'UTF-8', $_ ) } @ARGV;
GetOptions(
@@ -33,6 +43,7 @@ GetOptions(
'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,
@@ -46,9 +57,12 @@ GetOptions(
'timeout=i' => \$timeout,
'u|efa-url=s' => \$efa_url,
'v|via=s' => \$filter_via,
- 'V|track-via=s' => sub { $filter_via = $track_via = $_[1] },
- 'version' => \&show_version,
+ '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);
@@ -56,38 +70,87 @@ if ($list_services) {
show_services();
}
-if ( @ARGV != 2 ) {
+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};
}
for my $efield (@edata_pre) {
- given ($efield) {
- when ('a') { $edata{route_after} = 1; $full_routes = 1 }
- when ('b') { $edata{route_before} = 1; $full_routes = 1 }
- when ('f') { $edata{fullroute} = 1; $full_routes = 1 }
- when ('r') { $edata{route} = 1; $full_routes = 1 }
- default { $edata{$efield} = 1 }
- }
+ 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 ($service) {
- my $service_ref = first { lc( $_->{shortname} ) eq lc($service) }
- Travel::Status::DE::EFA::get_efa_urls();
+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 "
@@ -96,20 +159,25 @@ if ($service) {
);
exit 1;
}
- $efa_url = $service_ref->{url};
$efa_encoding = $service_ref->{encoding};
+ $efa_url = undef;
}
-sub new_efa_by_url {
- my ($url) = @_;
+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_url => $url,
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,
@@ -121,7 +189,7 @@ sub new_efa_by_url {
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;
@@ -129,8 +197,10 @@ sub show_help {
sub show_services {
printf( "%-45s %-14s %s\n\n", 'service', 'abbr. (-s)', 'url (-u)' );
- for my $service ( Travel::Status::DE::EFA::get_efa_urls() ) {
- printf( "%-45s %-14s %s\n", @{$service}{qw(name shortname url)} );
+ 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;
@@ -142,6 +212,14 @@ sub show_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) = @_;
@@ -152,25 +230,60 @@ sub format_route {
say 'BUG';
next;
}
- if ( not defined $stop->arr_time ) {
- $output .= sprintf( " %5s %40s %s\n",
- $stop->dep_time, $stop->name, $stop->platform, );
+ my $occupancy
+ = $stop->occupancy ? format_occupancy( $stop->occupancy ) : q{ };
+ my $delay = q{ };
+ if ( $stop->delay ) {
+ $delay = sprintf( '(%+3d)', $stop->delay );
}
- elsif ( not defined $stop->dep_time ) {
- $output .= sprintf( "%5s %40s %s\n",
- $stop->arr_time, $stop->name, $stop->platform, );
+ if ( $stop->is_cancelled ) {
+ $output .= sprintf(
+ " --:-- %s %s %35s %s\n",
+ $delay, $occupancy, $stop->full_name, $stop->platform // q{},
+ );
}
- elsif ( $stop->arr_time eq $stop->dep_time ) {
- $output .= sprintf( " %5s %40s %s\n",
- $stop->dep_time, $stop->name, $stop->platform, );
+ 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{},
+ );
+ }
}
- else {
+ elsif ( defined $stop->arr ) {
$output .= sprintf(
- "%5s → %5s %40s %s\n",
- $stop->arr_time, $stop->dep_time,
- $stop->name, $stop->platform,
+ "%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;
}
@@ -190,12 +303,12 @@ sub display_result {
for my $line (@lines) {
- if ( length( $line->[5] ) ) {
- $line->[5] =~ tr{\n\x0d}{ }s;
- chomp $line->[5];
+ if ( $edata{messages} and $line->[5]->hints ) {
print "\n";
- for my $info_line ( split( qr{\n}, $line->[5] ) ) {
- say "# ${info_line}";
+ for my $hint ( $line->[5]->hints ) {
+ $hint =~ tr{\n\x0d}{ }s;
+ chomp $hint;
+ say "# ${hint}";
}
}
@@ -212,23 +325,96 @@ sub display_result {
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,
+ $stop->platform
+ );
+ }
+}
+
sub show_lines {
my @output;
for my $l ( $efa->lines ) {
- if ( ( @grep_lines and not( $l->name ~~ \@grep_lines ) )
- or ( @grep_mots and not( $l->mot_name ~~ \@grep_mots ) ) )
+ if ( ( @grep_lines and none { $l->number eq $_ } @grep_lines )
+ or ( @grep_mots and none { $l->mot_name eq $_ } @grep_mots ) )
{
next;
}
- if ( @grep_mots and not( $l->mot_name ~~ \@grep_mots ) ) {
+ if ( @grep_mots and none { $l->mot_name eq $_ } @grep_mots ) {
next;
}
- push( @output,
- [ $l->type, $l->name, $l->direction // q{}, q{}, $l->route // q{} ]
+ push(
+ @output,
+ [
+ $l->type, $l->number,
+ $l->direction // q{}, q{},
+ $l->route // q{}
+ ]
);
}
@@ -237,9 +423,35 @@ sub show_lines {
return;
}
+sub format_occupancy {
+ my ($occupancy) = @_;
+
+ return $occupancy_map{$occupancy} // $occupancy_map{unknown};
+}
+
sub show_results {
my @output;
+ 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 ( 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;
@@ -247,18 +459,14 @@ sub show_results {
my $dtime = (
$relative_times
? sprintf( '%2d min', $d->countdown )
- : $d->sched_time
+ : $d->datetime->strftime('%H:%M')
);
- if ( $d->platform_db ) {
- $platform .= ' (DB)';
- }
-
if (
- ( @grep_lines and not( $d->line ~~ \@grep_lines ) )
- or ( @grep_mots and not( $d->mot_name ~~ \@grep_mots ) )
+ ( @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
@@ -273,28 +481,40 @@ sub show_results {
next;
}
else {
- $dtime .= ' CANCELED';
+ $dtime = '--:--';
}
}
- elsif ($track_via) {
+ elsif ($filter_via) {
my $via = first { $_->name =~ m{$filter_via}io } $d->route_post;
- $dtime .= ' → ' . $via->arr_time;
+ $dtime
+ .= ' → '
+ . $via->arr->clone->add( minutes => $d->delay // 0 )
+ ->strftime('%H:%M');
}
if ( $d->delay ) {
- if ($relative_times) {
- $dtime .= ' (+' . $d->delay . ')';
- }
- else {
- $dtime .= ' +' . $d->delay;
- }
+ $dtime .= ' ' . format_delay( $d->delay, $delay_len );
}
- @output_line
- = ( $dtime, $platform, $d->line, q{}, $d->destination, $d->info );
+ 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 );
+
+ if ($show_jid) {
+ $output_line[2] .= ' ' . $d->id;
+ }
if ( $edata{route} ) {
$output_line[3]
- = join( q{ }, map { $_->name_suf } $d->route_interesting );
+ = join( q{ }, map { $_->name } $d->route_interesting );
+ }
+ elsif ( $d->occupancy ) {
+ $output_line[3] = format_occupancy( $d->occupancy );
}
if ( $edata{fullroute} ) {
@@ -315,20 +535,33 @@ sub show_results {
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 ( $discover or $discover_and_print ) {
- for my $service_ref ( Travel::Status::DE::EFA::get_efa_urls() ) {
- $efa = new_efa_by_url( $service_ref->{url} );
+ 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}{qw(name shortname url shortname)},
- join( q{ }, map { "'$_'" } @ARGV ),
+ $service_ref->{name}, $shortname, $service_ref->{url},
+ $shortname, join( q{ }, map { "'$_'" } @ARGV ),
);
}
}
@@ -337,24 +570,48 @@ if ( $discover or $discover_and_print ) {
}
}
-$efa = new_efa_by_url($efa_url);
+$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:';
- say join( "\n", $efa->place_candidates );
+ 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:';
- say join( "\n", $efa->name_candidates );
+ 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->convert_blessed->encode( $efa->result );
+ }
+ else {
+ say JSON->new->convert_blessed->encode( [ $efa->results ] );
+ }
+}
+elsif ($raw_json_output) {
+ say JSON->new->convert_blessed->encode( $efa->{response} );
+}
+elsif ($coord) {
+ show_coord();
+}
+elsif ($stopfinder) {
+ show_stopfinder();
+}
+elsif ($stopseq) {
+ show_stopseq();
+}
+elsif ($list_lines) {
show_lines();
}
else {
@@ -365,58 +622,117 @@ __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<-Lr>] [B<-d> I<dd.mm.yyyy>] [B<-t> I<hh:mm>]
-[B<-l> I<lines>] [B<-p> I<platforms>] [B<-u> I<url>]
-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.17
+version 3.13
=head1 DESCRIPTION
-B<efa-m> lists scheduled tram, bus and train departures at the location I<name>
-in I<city>. Realtime data (i.e. delays) is included if available, it's
-visible in the output as a "+x" remark (meaning a delay of x minutes).
+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<-A>, B<--auto-url>, B<--discover-and-print>
+=item B<-A>, B<--auto-url>, B<--discover-and-print> (monitor)
-Probe all known EFA entry points for the specified stop. Print the first
-result which was not an error.
+Probe all known EFA services for the specified stop. Print the first result
+which was not an error.
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<-d>, B<--date> I<dd.mm.yyyy>
+=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>
+=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>
-Probe all known EFA entry points for the specified stop. Print the URLs and
-names of all entry points which did not return an error.
+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>
+=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>
+=item B<-l>, B<--line> I<lines> (monitor)
Only show departures of I<lines> (comma-separatad list, option may be
repeated)
@@ -426,7 +742,7 @@ repeated)
List supported EFA services with their URLs (see B<-u>) and abbreviations (see
B<-s>).
-=item B<-m>, B<--mot> I<motlist>
+=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).
@@ -435,11 +751,11 @@ 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>
+=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>
+=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
@@ -465,20 +781,37 @@ requested station.
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>
+=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<-r>, B<--relative>
+=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
@@ -486,36 +819,24 @@ already included.
=item B<-s>, B<--service> I<name>
-Short name of the EFA entry point. See Travel::Status::DE::EFA(3pm) and the
+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>
+=item B<-t>, B<--time> I<hh:mm> (monitor)
Show departures starting at I<time> instead of now.
-=item B<-u>, B<--efa-url> I<url>
-
-URL to the EFA entry point, defaults to
-L<http://efa.vrr.de/vrr/XSLT_DM_REQUEST>. Depending on your location, some
-I<url>s may contain more specific data than others. See
-Travel::Status::DE::EFA(3pm) and the B<--list> option for alternatives.
-
=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>
+=item B<-v>, B<--via> I<station> (monitor)
-Only show trains serving I<station> after the requseted stop. I<station>
-is matched against the "I<city> I<stop>" fields in each line's route.
-Regular expressions are also supported.
-
-=item B<-V>, B<--track-via> I<station>
-
-Lik B<--via>: Only show trains serving I<station> after the requseted stop.
-Also, show the arrival time at I<station> after the departure time at the
-current stop.
+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>
@@ -542,8 +863,6 @@ None.
=item * Travel::Status::DE::EFA(3pm)
-=item * XML::LibXML(3pm)
-
=back
=head1 BUGS AND LIMITATIONS
@@ -555,9 +874,17 @@ 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-2020 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt>
+Copyright (C) 2011-2023 Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt>
=head1 LICENSE