#!/usr/bin/perl -wT use strict; use IO::Socket; use IO::Select; my $switches = {}; my $tracks = {}; my $sensors = {}; my $stopped = 1; my $socket; sub transmit { if ($stopped) { print "(stopped, so not sending)\n"; } else { $socket->print(@_); } } # PORT sub getLightForTrack($$$) { # args are: this track's state, other track's state, will this track lead to the exit return 'red' if $_[0] eq 'building' or $_[1] eq 'building'; return 'red' if $_[0] eq 'arrival' and $_[2]; return 'white' if !$_[2]; return 'yellow' if $_[1] ne 'empty'; return 'green'; } sub getLightForArrivalTrack($$$) { # args are: selected track's state, other track's state, will selected track lead to the exit return 'red' if $_[0] ne 'empty'; return 'yellow' if $_[1] eq 'building' or !$_[2]; return 'green'; } sub redoPortYard() { if (!defined($switches->{'47'}) or !defined($switches->{'27'}) or !defined($switches->{'44'}) or !defined($tracks->{'port-track-1'}) or !defined($tracks->{'port-track-2'})) { return; } my $selectedTrack = ($switches->{'47'} eq 'CLOSED' or $switches->{'27'} eq 'OPEN') ? 'track1' : 'track2'; my $track1 = getLightForTrack($tracks->{'port-track-1'}, $tracks->{'port-track-2'}, $switches->{'44'} eq 'CLOSED'); my $track2 = getLightForTrack($tracks->{'port-track-2'}, $tracks->{'port-track-1'}, $switches->{'44'} ne 'CLOSED'); my $arrival = $selectedTrack eq 'track1' ? getLightForArrivalTrack($tracks->{'port-track-1'}, $tracks->{'port-track-2'}, $switches->{'44'} eq 'CLOSED') : getLightForArrivalTrack($tracks->{'port-track-2'}, $tracks->{'port-track-1'}, $switches->{'44'} ne 'CLOSED'); my $switcher = $tracks->{'port-track-1'} eq 'building' || $tracks->{'port-track-2'} eq 'building' ? 'white' : 'red'; print "Signal Controller: Setting port track 1 light to $track1.\n"; if ($track1 eq 'red') { transmit("SWITCH 207 OPEN\n"); } elsif ($track1 eq 'green') { transmit("SWITCH 207 CLOSED\n"); } elsif ($track1 eq 'white') { transmit("SWITCH 208 OPEN\n"); } elsif ($track1 eq 'yellow') { transmit("SWITCH 208 CLOSED\n"); } print "Signal Controller: Setting port track 2 light to $track2.\n"; if ($track2 eq 'red') { transmit("SWITCH 204 OPEN\n"); } elsif ($track2 eq 'green') { transmit("SWITCH 204 CLOSED\n"); } elsif ($track2 eq 'white') { transmit("SWITCH 205 OPEN\n"); } elsif ($track2 eq 'yellow') { transmit("SWITCH 205 CLOSED\n"); } print "Signal Controller: Setting port arrival light to $arrival.\n"; if ($arrival eq 'red') { transmit("SWITCH 200 OPEN\n"); } elsif ($arrival eq 'green') { transmit("SWITCH 200 CLOSED\n"); } elsif ($arrival eq 'yellow') { transmit("SWITCH 201 CLOSED\n"); } if ($arrival ne 'red') { my $warning = $selectedTrack eq 'track1' ? $track1 : $track2; $warning = 'yellow' if $warning eq 'white'; print "Signal Controller: Setting port arrival warning light to $warning.\n" if $arrival ne 'red'; if ($warning eq 'red') { transmit("SWITCH 202 OPEN\n"); } elsif ($warning eq 'green') { transmit("SWITCH 202 CLOSED\n"); } elsif ($warning eq 'yellow') { transmit("SWITCH 203 CLOSED\n"); } } print "Signal Controller: Setting switcher light to $arrival.\n"; if ($switcher eq 'red') { transmit("SWITCH 206 OPEN\n"); } else { transmit("SWITCH 206 CLOSED\n"); } } # STATIONS # XXX needs upgrading to handle three stations sub isOccupied($) { return ($sensors->{$_[0]} || 'OFF') eq 'ON'; } sub countOccupiedAndFree(@) { my $countOccupied = 0; my $countFree = 0; foreach (@_) { if (isOccupied($_)) { $countOccupied += 1; } else { $countFree += 1; } } return ($countOccupied, $countFree); } sub setPlatformLight($$) { print "Signal Controller: Would set platform $_[0] to state $_[1].\n"; } sub redoStations() { my $stations = [{ 'name' => 'Mission Bay', 'platforms' => ['1', '2'] }, { 'name' => 'Belmont', 'platforms' => ['11', '12', '13'] }]; $stations->[0]->{'other'} = $stations->[1]; $stations->[1]->{'other'} = $stations->[0]; foreach (@$stations) { ($_->{'occupied'}, $_->{'free'}) = countOccupiedAndFree(@{$_->{'platforms'}}); print "Signal Controller: $_->{'name'} has $_->{'occupied'} platform(s) occupied and $_->{'free'} free.\n"; } foreach my $station (@$stations) { foreach my $platform (@{$station->{'platforms'}}) { if (isOccupied($platform) and $station->{'other'}->{'free'} > 0) { setPlatformLight($platform, $station->{'occupied'} > 1 ? 'yellow' : 'green'); } else { setPlatformLight($platform, 'red'); } } } } # MAIN my $needToRedo = {}; sub checkRedos() { foreach (values %$needToRedo) { if ($_->{'time'} - time <= 0) { &{$_->{'code'}}(); $_->{'retries'} += 1; $_->{'time'} = time + 2 * $_->{'retries'}; } } } sub timeRemaining() { my $minimum; foreach (values %$needToRedo) { if (!defined($minimum) or $minimum > $_->{'time'}) { $minimum = $_->{'time'}; } } my $time = $minimum - time; return $time < 0 ? 0 : $time; } sub scheduleRedo($$) { $needToRedo->{$_[0]} = { 'time' => time, 'code' => $_[1], 'retries' => 0, }; } # MAIN while (1) { print "Signal Controller: Connecting to Train Server...\n"; $socket = IO::Socket::INET->new(PeerAddr => 'localhost', PeerPort => 6051, Proto => 'tcp'); if ($socket) { $| = 1; $socket->print("TOP2 Signal Controller\n"); $socket->print("USER anonymous\n"); $socket->print("PASSWORD -\n"); $socket->print("STATUS\n"); print "Signal Controller: Connected to Train Server.\n"; my $select = IO::Select->new(); $select->add($socket); eval { while (1) { my @ready; if (!$stopped and grep { $_ > 0 } values %$needToRedo) { @ready = $select->can_read(timeRemaining); } else { @ready = $select->can_read(); } if (!@ready) { checkRedos(); next; } elsif (not grep { $_ == $socket } @ready) { next; } $_ = <$socket>; chomp; m/^GO$/ && do { $stopped = 0; next; }; m/^STOP$/ && do { $stopped = 1; next; }; m/^SWITCH (\d+) (OPEN|CLOSED)$/ && do { $switches->{$1} = $2; if ($1 eq '47' or $1 eq '27' or $1 eq '44') { print "Signal Controller: Received state of Union Pacific port switch $1 ($2).\n"; scheduleRedo('portYard', \&redoPortYard); } next; }; m/^MESSAGE STATUS ([-a-zA-Z0-9]+) ([-a-zA-Z0-9]+)$/ && do { if ($2 eq 'UNKNOWN') { print "Signal Controller: Received query for track $1.\n"; if (exists($tracks->{$1})) { print "Signal Controller: Track $1 is known; status is $tracks->{$1}.\n"; $socket->print("MESSAGE STATUS $1 $tracks->{$1}\n"); } } elsif (!exists($tracks->{$1}) or $tracks->{$1} ne $2) { $tracks->{$1} = $2; print "Signal Controller: Track $1 set to state $2.\n"; if ($1 eq 'port-track-1' or $1 eq 'port-track-2') { scheduleRedo('portYard', \&redoPortYard); } } next; }; # m/^SENSOR ([0-9]+) (ON|OFF)( STILL)?$/ && do { # my $before = $sensors->{$1} || 'UNKNOWN'; # $sensors->{$1} = $2; # if ($before ne $sensors->{$1}) { # if ($1 eq '1' or $1 eq '2' or # $1 eq '11' or $1 eq '12' or $1 eq '13') { # print "Signal Controller: Platform sensor $1 is now $2.\n"; # scheduleRedo('stations', \&redoStations); # } # } # next; # }; m/^ERROR/ && do { warn "$_\n"; next; }; next; } }; if ($@) { warn "$@\n"; } print "Signal Controller: Disconnected from Train Server.\n"; } else { warn "Signal Controller: $!\n"; } sleep 1; }