# ============================================================================
# FILE: DnB_GradeCrossing.pm                                        6/23/2019
#
# SERVICES:  DnB GRADE CROSSING FUNCTIONS
#
# DESCRIPTION:
#    This perl module provides grade crossing related functions used by the 
#    DnB model railroad control program.
#
# PERL VERSION: 5.24.1
#
# =============================================================================

# -----------------------------------------------------------------------------
# Package Declaration
# -----------------------------------------------------------------------------

package DnB_GradeCrossing;
require Exporter;
@ISA = qw(Exporter);

@EXPORT = qw(
   ProcessGradeCrossing
   GcChildProcess
   TestGradeCrossing
);

use DnB_Sensor;
use DnB_Signal;
use DnB_Turnout;
use DnB_Message;
use Forks::Super;

# =============================================================================
# FUNCTION:  ProcessGradeCrossing
#
# DESCRIPTION:
#    This routine is used to process the specified grade crossing. It is called
#    once an iteration by the main program loop. State data that is used for 
#    grade crossing control is persisted in the %GradeCrossingData hash. Each 
#    grade crossing is in one of the following states; 'idle', 'gateLower', 
#    'approach', 'road', 'gateRaise' or 'depart'. %GradeCrossingData values, 
#    sensor bits, and code within this routine, transition the signal through 
#    these states. Operation is as follows.
#
#    1. Configuration and initializations set in %GradeCrossingData hash.
#
#    2. In 'idle' state, a train approaching the grade crossing is detected by 
#    sensors 'AprEast', 'AprWest', or 'Road'. This causes the signals to begin 
#    flashing. 'SigRun' is set to 'on'. 'GateDelay' is set and the state 
#    transitions to 'gateLower'.
#
#    3. In 'gateLower' state, GateDelay is performed and the 'AprTimer' is set.
#    If gates are available, they are lowered. Then the state transitions to 
#    'approach'. The GateDelay value is used to better simulate proto-typical
#    signal operation.
#
#    4. In 'approach' state, if 'road' state is not achieved before 'AprTimer' 
#    expires, the code transitions to the 'gateRaise' state. This could occur if 
#    the train stops or backs away before reaching the 'Road' sensor. An active 
#    'Road' sensor causes transition to the 'road' state.
#
#    5. In 'road' state, a short timeout is set into 'RoadTimer'. Additional 
#    'Road' sensor activity reloads this timer. This maintains 'road' state 
#    while the train occupies the grade crossing. When no further 'Road' sensor 
#    activity is reported, 'RoadTimer' will expire. The state transitions to
#    'gateRaise'.
#
#    6. In 'gateRaise' state, if grade crossing does not have gates, 'DepTimer'
#    is set and the state transitions to 'depart'. Otherwise, the gates are 
#    raised. Once completed (servo pid == 0), 'DepTimer' is set and the state 
#    transitions to 'depart'.
#
#    7. In the 'depart' state, the signal lamp flashing is stopped and 'SigRun' 
#    is set to 'off'. Outbound train 'AprEast' or 'AprWest' sensor activity 
#    restarts the 'DepTimer' maintaining the 'depart' state. Once the last car 
#    of the outbound train is past the 'AprEast' or 'AprWest' sensor, the 
#    'DepTimer' expires and the state transitions to 'idle'. 
#
#    If the train backs up, 'Road' sensor activity will transition the state to
#    'idle'. From 'idle', the active 'Road' sensor will start a new signaling 
#    cycle. 
#
# CALLING SYNTAX:
#    $result = &ProcessGradeCrossing($gc, \%GradeCrossingData, \%SensorBit,
#              \%TurnoutData, \%MCP23017, \%SensorState);
#
# ARGUMENTS:
#    $Gc                 Index to data in %GradeCrossingData.
#    $GradeCrossingData  Pointer to %GradeCrossingData hash.
#    $SensorBit          Pointer to %SensorBit hash.
#    $TurnoutData        Pointer to %TurnoutData hash. (needed for gates and sound)
#    $MCP23017           Pointer to %MCP23017 hash. (GPIO definitions)
#    $SensorState        Pointer to %SensorState hash.
#
# RETURNED VALUES:
#    0 = Success,  1 = Error
#
# ACCESSED GLOBAL VARIABLES:
#    None.
# =============================================================================
sub ProcessGradeCrossing {
   my($Gc, $GradeCrossingData, $SensorBit, $TurnoutData, $MCP23017, 
      $SensorState) = @_;
   my($result, @gates, $gate);

# Isolate the current grade crossing sensor bit values and get the current time.

   my($aprEastSensor) = &GetSensorBit($$GradeCrossingData{$Gc}{'AprEast'}, 
                                      $SensorBit, $SensorState);
   my($roadSensor) = &GetSensorBit($$GradeCrossingData{$Gc}{'Road'}, $SensorBit, 
                                   $SensorState);
   my($aprWestSensor) = &GetSensorBit($$GradeCrossingData{$Gc}{'AprWest'}, 
                                      $SensorBit, $SensorState);
   my($cTime) = time;

   $result = &DisplayDebug(2, "ProcessGradeCrossing $Gc, State: " .
             "$$GradeCrossingData{$Gc}{'State'}   aprEastSensor: $aprEastSensor" .
             "   roadSensor: $roadSensor   aprWestSensor: $aprWestSensor   " .
             "cTime: $cTime");

# Idle state code. ------------------------------------------------------------
   if ($$GradeCrossingData{$Gc}{'State'} eq 'idle') {
      if ($roadSensor == 1 or $aprEastSensor == 1 or $aprWestSensor == 1) {
         if ($$GradeCrossingData{$Gc}{'SigRun'} ne 'on') {
            $result = &DisplayMessage("ProcessGradeCrossing $Gc, '" .
                      $$GradeCrossingData{$Gc}{'State'} . 
                      "' start signals");

            # Start lamps and approach sound effect.
            Forks::Super::write_stdin($$GradeCrossingData{$Gc}{'Pid'}, 
               'start:apr');
         }
         
         $$GradeCrossingData{$Gc}{'SigRun'} = 'on';
         $$GradeCrossingData{$Gc}{'GateDelay'} = $cTime + .5;
         $$GradeCrossingData{$Gc}{'State'} = 'gateLower';
         $result = &DisplayMessage("ProcessGradeCrossing $Gc, 'idle' --> " .
                      "'$$GradeCrossingData{$Gc}{'State'}'.");
      }
   }

# GateLower state code. --------------------------------------------------------
   if ($$GradeCrossingData{$Gc}{'State'} eq 'gateLower') {

      # Wait GateDelay. If gates are available, lower them. Then transition
      # to approach state.
      if ($$GradeCrossingData{$Gc}{'GateDelay'} < $cTime) { # Delay time done?
         if ($$GradeCrossingData{$Gc}{'Gate'} ne '') {
            @gates = split(",", $$GradeCrossingData{$Gc}{'Gate'});
            foreach $gate (@gates) {
               $result = &DisplayMessage("ProcessGradeCrossing $Gc, '" .
                         $$GradeCrossingData{$Gc}{'State'} . " state' close " .
                         "gate: $gate");
               $result = &MoveTurnout('Close', $gate, $TurnoutData);
            }
         }
         $$GradeCrossingData{$Gc}{'AprTimer'} = $cTime + 10;
         $$GradeCrossingData{$Gc}{'State'} = 'approach';
         $result = &DisplayMessage("ProcessGradeCrossing $Gc, 'gateLower' --> " .
                      "'$$GradeCrossingData{$Gc}{'State'}'.");
      }
   }

# Approach state code. --------------------------------------------------------
   if ($$GradeCrossingData{$Gc}{'State'} eq 'approach') {
      if ($roadSensor == 1) {
         $$GradeCrossingData{$Gc}{'RoadTimer'} = $cTime + 1;    # Set RoadTimer
         $$GradeCrossingData{$Gc}{'State'} = 'road';
         $result = &DisplayMessage("ProcessGradeCrossing $Gc, 'approach' --> " .
                      "'$$GradeCrossingData{$Gc}{'State'}'.");

         # Change to roadside sound effect. Commented out, need better sound
         # module.       
#        Forks::Super::write_stdin($$GradeCrossingData{$Gc}{'Pid'}, 'start:road');
      }
      elsif ($$GradeCrossingData{$Gc}{'AprTimer'} < $cTime) { # AprTimer timeout?
         $$GradeCrossingData{$Gc}{'State'} = 'gateRaise';
         $result = &DisplayMessage("ProcessGradeCrossing $Gc, 'approach' " .
                   "==> '$$GradeCrossingData{$Gc}{'State'}'.");
      }
   }

# Road state code. ------------------------------------------------------------
   if ($$GradeCrossingData{$Gc}{'State'} eq 'road') {
      if ($roadSensor == 1) {
         $$GradeCrossingData{$Gc}{'RoadTimer'} = $cTime + 1;  # Update RoadTimer
      }
      else {
         if ($$GradeCrossingData{$Gc}{'RoadTimer'} < $cTime) { # timeout?
            $$GradeCrossingData{$Gc}{'State'} = 'gateRaise';
            $result = &DisplayMessage("ProcessGradeCrossing $Gc, 'road' --> " .
                      "'$$GradeCrossingData{$Gc}{'State'}'.");

            # Set back to approach sound effect. Commented out, road sound not
            # currently used.
            # Forks::Super::write_stdin($$GradeCrossingData{$Gc}{'Pid'}, 
            # 'start:apr');
         }
      }
   }

# GateRaise state code. --------------------------------------------------------
   if ($$GradeCrossingData{$Gc}{'State'} eq 'gateRaise') {

      # If no gates, transition to depart state.
      if ($$GradeCrossingData{$Gc}{'Gate'} eq '') {
         $$GradeCrossingData{$Gc}{'DepTimer'} = $cTime + 1;    # Set DepTimer
         $$GradeCrossingData{$Gc}{'State'} = 'depart';
         $result = &DisplayMessage("ProcessGradeCrossing $Gc, 'gateRaise' --> " .
                      "'$$GradeCrossingData{$Gc}{'State'}'.");
      }
      else {
         if ($$GradeCrossingData{$Gc}{'GateServo'} == 0) {
            @gates = split(",", $$GradeCrossingData{$Gc}{'Gate'});
            foreach $gate (@gates) {
               $result = &DisplayMessage("ProcessGradeCrossing $Gc, '" .
                         $$GradeCrossingData{$Gc}{'State'} . " state' open " .
                         "gate: $gate");
               $result = &MoveTurnout('Open', $gate, $TurnoutData);
            }
            $$GradeCrossingData{$Gc}{'GateServo'} = $gates[0];
            $result = &DisplayMessage("ProcessGradeCrossing $Gc, '" .
                      $$GradeCrossingData{$Gc}{'State'} . " state' waiting for " .
                      "gate $$GradeCrossingData{$Gc}{'GateServo'} to open.");
         }
         elsif ($$TurnoutData{$$GradeCrossingData{$Gc}{'GateServo'}}{Pid} == 0) {
            $$GradeCrossingData{$Gc}{'GateServo'} = 0;
            $$GradeCrossingData{$Gc}{'DepTimer'} = $cTime + 1;  # Set DepTimer
            $$GradeCrossingData{$Gc}{'State'} = 'depart';
            $result = &DisplayMessage("ProcessGradeCrossing $Gc, 'gateRaise' " .
                      "--> '$$GradeCrossingData{$Gc}{'State'}'.");
         }
      }
   }

# Depart state code. ----------------------------------------------------------
   if ($$GradeCrossingData{$Gc}{'State'} eq 'depart') {
      if ($$GradeCrossingData{$Gc}{'SigRun'} ne 'off') {
         $result = &DisplayMessage("ProcessGradeCrossing $Gc, '" .
                                   $$GradeCrossingData{$Gc}{'State'} . 
                                   "' stop signals");
         Forks::Super::write_stdin($$GradeCrossingData{$Gc}{'Pid'}, 'stop');
         $$GradeCrossingData{$Gc}{'SigRun'} = 'off';
      }

      # If roadSensor sets, the train backed up. Transition to idle state to
      # start a new grade crossing cycle.   
      if ($roadSensor == 1) {
         $$GradeCrossingData{$Gc}{'State'} = 'idle';
         $result = &DisplayMessage("ProcessGradeCrossing $Gc, 'depart' ==> " .
                      "'$$GradeCrossingData{$Gc}{'State'}'.");
      }

      # Stay in depart state until approach sensors are inactive. This prevents
      # the start of a new grade crossing cycle by departing train. We also
      # get here if an approach sensor is blocked by a stopped train.
      elsif ($aprEastSensor == 1 or $aprWestSensor == 1) {
         $$GradeCrossingData{$Gc}{'DepTimer'} = $cTime + 1;   # Set DepTimer
      }

      # Transition to idle state after DepTimer expires.
      elsif ($$GradeCrossingData{$Gc}{'DepTimer'} < $cTime) {
         $$GradeCrossingData{$Gc}{'State'} = 'idle';
         $result = &DisplayMessage("ProcessGradeCrossing $Gc, 'depart' --> " .
                      "'$$GradeCrossingData{$Gc}{'State'}'.");
      }
   }

   return 0;
}

# =============================================================================
# FUNCTION:  GcChildProcess
#
# DESCRIPTION:
#    This routine is launched as a child process during main program startup
#    and is used to start and stop grade crossing signal lamp flash operation.
#    Since Forks::Super does not allow a child to fork to another child, any
#    servo driven gate timing and positioning for the signal must be done by 
#    the caller.
#
#    A dedicated GcChildProcess is started for each grade crossing. The returned
#    child Pid value is stored in the %GradeCrossingData hash. This Pid value 
#    is used in the Forks::Super::write_stdin message to send commands to the
#    proper GcChildProcess instance. 
#
# CALLING SYNTAX:
#    $pid = fork { os_priority => 1, sub => \&GcChildProcess, 
#                  child_fh => "in socket",
#                  args => [ $Gc, $SignalChildPid, \%GradeCrossingData,
#                            \%SensorChip, \%MCP23017 ] };
#
#       $GradeCrossing       The signal to be processed.
#       $SignalChildPid      PID of child signal refresh process.
#       $GradeCrossingData   Pointer to the %GradeCrossingData hash.
#       $SensorChip			 Pointer to the %SensorChip hash.
#       $MCP23017            Pointer to the %MCP23017 hash.
#
#    The SuperForks 'child_fh' functionality is used for communication between 
#    the parent and child processes. The parent sends a start/stop signal message
#    to the child's stdin. The message must be formatted as follows.
#
#       start:apr  - Start flashing lamps with bell sound 1.
#       start:road - Start flashing lamps with bell sound 2.
#       stop       - Stop lamp flash and bell sound.
#       exit       - Terminate GcChildProcess.
#
# SEND DATA TO CHILD:
#    Forks::Super::write_stdin($GcChildPid, 'start:apr'));
#    Forks::Super::write_stdin($GcChildPid, 'start:road'));
#    Forks::Super::write_stdin($GcChildPid, 'stop'));
#    Forks::Super::write_stdin($GcChildPid, 'exit'));
#
# RETURNED VALUES:
#    PID of child process = Success, 0 = Error
#
# ACCESSED GLOBAL VARIABLES:
#    $main::ChildName
# =============================================================================
sub GcChildProcess {
   my($GradeCrossing, $SignalChildPid, $GradeCrossingData, $SensorChip,
      $MCP23017) = @_;
   my($result, $x, @buffer, $lampColor, $sndSet, $sndClr, $data);
   my($cmd) = '';  my($lampFlash) = 0;
   my(%sound) = ('apr' => '', 'road' => '');
   $main::ChildName = "GcChildProcess$GradeCrossing";
   $result = &DisplayMessage("GcChildProcess${GradeCrossing} started.");

# Setup grade crossing specific working variables.
   my($signalNmbr) = $$GradeCrossingData{$GradeCrossing}{Signal};
   if ($$GradeCrossingData{$GradeCrossing}{'SoundApr'} =~ 
         m/^(\d),(GPIO)(.)(\d)$/) {
      $sound{'apr'}{'chip'} = $1;
      $sound{'apr'}{'port'} = join("", $2, $3);
      $sound{'apr'}{'gpio'} = join("", $2, $3, $4);
      $sound{'apr'}{'olat'} = join("", "OLAT", $3);
      $sound{'apr'}{'bitSet'} = 1 << $4;
      $sound{'apr'}{'bitClr'} = ~$sound{'apr'}{'bitSet'};
   }
   if ($$GradeCrossingData{$GradeCrossing}{'SoundRoad'} =~ 
         m/^(\d),(GPIO)(.)(\d)$/) {
      $sound{'road'}{'chip'} = $1;
      $sound{'road'}{'port'} = join("", $2, $3);
      $sound{'road'}{'gpio'} = join("", $2, $3, $4);
      $sound{'road'}{'olat'} = join("", "OLAT", $3);
      $sound{'road'}{'bitSet'} = 1 << $4;
      $sound{'road'}{'bitClr'} = ~$sound{'road'}{'bitSet'};
   }
   $result = &DisplayDebug(2, "GcChildProcess${GradeCrossing}, using " .
                              "signalNmbr: $signalNmbr gates: $gates " .
                              "sndApr: '" . $sound{'apr'}{'gpio'} . "' " .
                              "sndRoad: '" . $sound{'road'}{'gpio'} . "'");

# Run the main processing loop.
   while (1) {
      push(@buffer, <STDIN>);
#      if ($#buffer >= 0) {
#         for ($x = 0; $x <= $#buffer; $x++) {
#            print "x: $x - '$buffer[$x]' \n";
#         }
#      }

      # ----------
      # Check for a new complete message and process if found.
      if ($buffer[0] =~ m/(start):(apr)/i or $buffer[0] =~ m/(start):(road)/i or 
          $buffer[0] =~ m/(stop)/i or $buffer[0] =~ m/(exit)/i) {
         $cmd = lc $1;
         $sndSet = lc $2;
         
         if ($sndSet eq 'apr') {
            $sndClr = 'road';
         }
         elsif ($sndSet eq 'road') {
            $sndClr = 'apr';
         }
         else {
            $sndClr = '';
         }    
         splice(@buffer, 0, 1);         # Remove processed record.
           
#         $result = &DisplayDebug(3, "GcChildProcess${GradeCrossing}, cmd: " .
#                                    "'$cmd'   sndSet: '$sndSet'");
      }

      # ----------
      # Process new command, if any.
      if ($cmd ne "") {
         if ($cmd eq "start") {
            if ($lampFlash == 0) {
               $lampColor = 'Red';
               $lampFlash = 1;
            }

            # Clear opposite sound activation control bit
            if ($sndClr ne '') {
               $result = &clearBit($sndClr, \%sound, $SensorChip);
            }   
                           
            # Set new sound activation control bit.
            if ($sndSet ne '' and exists($sound{$sndSet}{'chip'})) {
               $data = $$SensorChip{ $sound{$sndSet}{'chip'} }{'Obj'}
                       ->read_byte($$MCP23017{ $sound{$sndSet}{'port'} });
               $data = $data | $sound{$sndSet}{'bitSet'};
               $$SensorChip{ $sound{$sndSet}{'chip'} }{'Obj'}
               ->write_byte($data, $$MCP23017{ $sound{$sndSet}{'olat'} });
            }
         }
         elsif ($cmd eq "stop" and $lampFlash == 1) {
            $lampColor = 'Off';
         }
         elsif ($cmd eq "exit") {
            $result = &DisplayMessage("GcChildProcess${GradeCrossing} " .
                                      "commanded to exit.");

            # Turn off signal lamps.
            $result = &SetSignalColor($signalNmbr, 'Off', $SignalChildPid, 
                                      $SignalData, '');

            # Clear sound activation control bits
            $result = &clearBit('apr', \%sound, $SensorChip);
            $result = &clearBit('road', \%sound, $SensorChip);
            last;       # Break out of while loop and exit.
         }
         $cmd = "";                     # Remove processed command.
      }

      # ----------
      # Change lamp state.
      if ($lampFlash == 1) {
         if ($lampColor eq 'Off') {
            $lampFlash = 0;

            # Clear sound activation control bits
            $result = &clearBit('apr', \%sound, $SensorChip);
            $result = &clearBit('road', \%sound, $SensorChip);
         }
         elsif ($lampColor eq 'Red') {
            $lampColor = 'Grn';
         }
         else {
            $lampColor = 'Red';
         }

         if (&SetSignalColor($signalNmbr, $lampColor, $SignalChildPid, 
                             $SignalData, '')) {
            $result = &DisplayError("GcChildProcess${GradeCrossing}, " .
                                    "SetSignalColor returned error.");
         }
      }
      select (undef, undef, undef, .8);    # Sets signal flash rate.
   }
   $result = &DisplayMessage("GcChildProcess${GradeCrossing} terminated.");
   exit(0);
   
   # ==========
   # GcChildProcess private subroutine for clearing the specified sound
   # activation control bit. 
   # ==========
   sub clearBit{
      my($Snd, $SoundHash, $SensorChip) = @_;
      my($data);
   
      if (exists($$SoundHash{$Snd}{'chip'})) {
         $data = $$SensorChip{ $$SoundHash{$Snd}{'chip'} }{'Obj'}
                 ->read_byte($$MCP23017{ $$SoundHash{$Snd}{'port'} });
         $data = $data & $$SoundHash{$Snd}{'bitClr'};          
         $$SensorChip{ $$SoundHash{$Snd}{'chip'} }{'Obj'}
         ->write_byte($data, $$MCP23017{ $$SoundHash{$Snd}{'olat'} });
      }
      return 0;
   }
}

# =============================================================================
# FUNCTION:  TestGradeCrossing
#
# DESCRIPTION:
#    This routine cycles the specified grade crossing signal ranges.
#
# CALLING SYNTAX:
#    $result = &TestGradeCrossing($Range, \%GradeCrossingData, \%TurnoutData);
#
# ARGUMENTS:
#    $Range               Signal number or range to use.
#    $GradeCrossingData   Pointer to GradeCrossingData hash.
#    $TurnoutData         Pointer to %TurnoutData hash.
#
# RETURNED VALUES:
#    0 = Success,  1 = Error.
#
# ACCESSED GLOBAL VARIABLES:
#    $main::MainRun
# =============================================================================
sub TestGradeCrossing {

   my($Range, $GradeCrossingData, $TurnoutData) = @_;
   my($result, $gc, @gates, $gate);
   my(@gcList) = split(",", $Range);

   $result = &DisplayDebug(2, "TestGradeCrossing, Entry ...   Range: '$Range'" .
                              "   gcList: @gcList");

   while ($main::MainRun) {

   # Start approach signal.
      foreach $gc (@gcList) {
         $gc = "0${gc}" if (length($gc) == 1);
         if (exists $$GradeCrossingData{$gc}) {
            $result = &DisplayMessage("TestGradeCrossing, start:apr grade " .
                                      "crossing $gc");
            Forks::Super::write_stdin($$GradeCrossingData{$gc}{'Pid'}, 
                                      'start:apr');
            select (undef, undef, undef, 1);    # Time for realistic lamp start.
         }
         else {
            $result = &DisplayError("TestGradeCrossing, invalid grade " .
                                    "crossing: $gc");
            return 1;
         }

   # Lower gates if grade crossing is so equipt.
         @gates = split(",", $$GradeCrossingData{$gc}{'Gate'});
         foreach $gate (@gates) {
            $result = &DisplayDebug(1, "TestGradeCrossing, Close gate: $gate");
            $result = &MoveTurnout('Close', $gate, $TurnoutData);
            if ($result == 1) {
               $result = &DisplayDebug(1, "TestGradeCrossing, gate: $gate " .
                                          "returned error.");
            }
            elsif ($result == 2) {
               $result = &DisplayDebug(1, "TestGradeCrossing, gate: $gate " .
                                          "returned already in position.");
            }
         }
         Forks::Super::pause 2;
      }
      Forks::Super::pause 4;

# Change to 'road' grade crossing sound. Commented out, need better sound module.
      foreach $gc (@gcList) {
         $gc = "0${gc}" if (length($gc) == 1);
#         $result = &DisplayMessage("TestGradeCrossing, start:road grade " .
#                                   "crossing $gc");
#        Forks::Super::write_stdin($$GradeCrossingData{$gc}{'Pid'}, 'start:road');
      }
       Forks::Super::pause 4;

# Stop signal.
      foreach $gc (@gcList) {
         $gc = "0${gc}" if (length($gc) == 1);
         $result = &DisplayMessage("TestGradeCrossing, stop grade crossing $gc");
         @gates = split(",", $$GradeCrossingData{$gc}{'Gate'});
         foreach $gate (@gates) {
            $result = &MoveTurnout('Open', $gate, $TurnoutData);
            if ($result == 1) {
               $result = &DisplayDebug(1, "TestGradeCrossing, gate: $gate " .
                                          "returned error.");
            }
            elsif ($result == 2) {
               $result = &DisplayDebug(1, "TestGradeCrossing, gate: $gate " .
                                          "returned already in position.");
            }
         }

         # If gates for this crossing, wait for gate open to complete before
         # stopping lamp flash.
         if ($#gates >= 0) {
            $result = &DisplayDebug(1, "TestGradeCrossing, waiting for gate " .
                                       "$gates[0] move to complete.");
            while ($$TurnoutData{$gates[0]}{Pid} > 0) {
               select (undef, undef, undef, .5);
            }
         }
         Forks::Super::write_stdin($$GradeCrossingData{$gc}{'Pid'}, 'stop');
         Forks::Super::pause 2;
      }
      Forks::Super::pause 4;
   }
   return 0;
}

return 1;
