# ============================================================================
# FILE: DnB_Yard.pm                                                 7/10/2019
#
# SERVICES:  DnB TRACK PROCESSING FUNCTIONS
#
# DESCRIPTION:
#    This perl module provides yard track processing related functions used 
#    by the DnB model railroad control program. 
#
# PERL VERSION: 5.24.1
#
# =============================================================================

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

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

@EXPORT = qw(
   GetYardRoute
   YardRoute
   TestSound
   TestRelay
);

use DnB_Message;
use DnB_Sensor;
use DnB_Turnout;

# =============================================================================
# FUNCTION:  GetYardRoute
#
# DESCRIPTION:
#    This routine processes yard route keypad input from the user. Input is
#    obtained by reading the stderr output of the KeypadChild process. This
#    data is a single character, 0 through F, corresponding to track numbers
#    1 through 16. Two track numbers define a from/to route. A route is valid
#    if present in the %YardRouteData hash. The route identifier is set for 
#    processing by the YardRoute routine. Special case routes are also handled
#    here. 
#
# CALLING SYNTAX:
#    $result = &GetYardRoute(\%YardRouteData, \%KeypadData, \%GpioData,
#                            $KeypadChildPid);
#
# ARGUMENTS:
#    $YardRouteData      Pointer to %YardRouteData hash.
#    $KeypadData         Pointer to %KeypadData hash.
#    $GpioData           Pointer to %GpioData hash.
#    $KeypadChildPid     Pid of Keypad child process.
#
# RETURNED VALUES:
#    0 = Success,  1 = Error.
#
# ACCESSED GLOBAL VARIABLES:
#    None.
# =============================================================================
sub GetYardRoute {
   my($YardRouteData, $KeypadData, $GpioData, $KeypadChildPid) = @_;
   my($result, $pressedKey, $route, $altRoute);
   my($keypadId) = '01';
   my($cTime) = time;

   $result = &DisplayDebug(2, "GetYardRoute entry ...");

   if ($$YardRouteData{'Control'}{'Inprogress'} == 0) {
      $pressedKey = Forks::Super::read_stderr($KeypadChildPid);

      if ($pressedKey ne '') {
         $pressedKey = substr($pressedKey, 0, 1);      # 1st character only.
         $result = &DisplayDebug(1, "GetYardRoute, pressedKey: $pressedKey");

         if ($$KeypadData{$keypadId}{'Entry1'} == -1) {
            $$KeypadData{$keypadId}{'Entry1'} = $pressedKey;

            # Turn on 1st entry LED.
            $$GpioData{ $$KeypadData{$keypadId}{'Gpio'} }{'Obj'}->write(1);
            $$KeypadData{$keypadId}{'PressTime'} = $cTime + 5;
            $result = &PlaySound("C.wav");
         }
         else {      # Got 'from' and 'to' entries.
            $route = join('', 'R', $$KeypadData{$keypadId}{'Entry1'}, 
                          $pressedKey);
            $altRoute = join('', 'r', $$KeypadData{$keypadId}{'Entry1'}, 
                             $pressedKey);
            if (exists $$YardRouteData{$route}) {
               $result = &PlaySound("G.wav");

               # Handle special route cases which involve %YardRouteData
               # entries with Rxx and rxx keys. A consecutive route entry
               # uses the alternate route key if available.
               if (exists($$YardRouteData{$altRoute}) and
                   $$YardRouteData{'Control'}{'Route'} eq $route) {
                  $route = $altRoute;
               }

               # Initiate turnout setting for specified route.
               $$YardRouteData{'Control'}{'Route'} = $route;   
               $$YardRouteData{'Control'}{'Inprogress'} = 1;
               $$YardRouteData{'Control'}{'Step'} = 0;
            }
            else {
               $result = &PlaySound("CA.wav");
            }

            # Turn off 1st entry LED.
            $$GpioData{ $$KeypadData{$keypadId}{'Gpio'} }{'Obj'}->write(0);  
            $$KeypadData{$keypadId}{'Entry1'} = -1;
         }
      }
      elsif ($$KeypadData{$keypadId}{'Entry1'} != -1) {

         # Abort 1st entry if a second keypress is not entered before 
         # timeout expiration.
         if ($cTime > $$KeypadData{$keypadId}{'PressTime'}) {

            # Turn off 1st entry LED.
            $$GpioData{ $$KeypadData{$keypadId}{'Gpio'} }{'Obj'}->write(0); 
            $$KeypadData{$keypadId}{'Entry1'} = -1;            
         } 
      }
   } 
   return 0;
}

# =============================================================================
# FUNCTION:  YardRoute
#
# DESCRIPTION:
#    This routine performs the operational functions related to yard trackage
#    routing. Only one turnout of a valid route list is positioned for each 
#    call to minimize CPU loading. 'Inprogress' is reset when all turnouts for
#    the route have be positioned.
#
# CALLING SYNTAX:
#    $result = &YardRoute(\%YardRouteData, \%TurnoutData);
#
# ARGUMENTS:
#    $YardRouteData      Pointer to %YardRouteData hash.
#    $TurnoutData        Pointer to %TurnoutData hash.
#
# RETURNED VALUES:
#    0 = Success,  1 = Error.
#
# ACCESSED GLOBAL VARIABLES:
#    None.
# =============================================================================
sub YardRoute {
   my($YardRouteData, $TurnoutData) = @_;
   my($result, $route, @routeList, $step, $turnout, $position);

   $result = &DisplayDebug(2, "YardRoute entry ...");

   if ($$YardRouteData{'Control'}{'Inprogress'} == 1) {
      $route = $$YardRouteData{'Control'}{'Route'};
      if ($route ne "") {
         @routeList = split(',', $$YardRouteData{$route});
         $result = &DisplayDebug(2, "YardRoute, route: $route   routeList: " .
                                    "@routeList");
         if ($#routeList >= 0) {
            $step = $$YardRouteData{'Control'}{'Step'};
            if ($step <= $#routeList) {
               if ($routeList[$step] =~ m/^T(\d\d):(.+)/) {
                  $turnout = $1;
                  $position = $2;
                  $result = &DisplayMessage("YardRoute, Route: $route, Step: " .
                                            "$step - $turnout:$position");
                  $$YardRouteData{'Control'}{'Step'}++;   # Increment step.
                  $result = &MoveTurnout($position, $turnout, $TurnoutData);
                  if ($result == 1) {
                     $result = &DisplayError("YardRoute, Failed to set turnout " .
                                             "$turnout to $position");
                  }
               }
               else {
                  $result = &DisplayError("YardRoute, Invalid route: $route " .
                                          "step: $step.");
                  $$YardRouteData{'Control'}{'Route'} = "";
                  $$YardRouteData{'Control'}{'Inprogress'} = 0;
               }
            }
            else {                          # === Route is fully processed. ===
               $$YardRouteData{'Control'}{'Inprogress'} = 0;   
               # Retain 'Route'. Last needed for detection of special cases.
            }
         }
         else {
            $result = &DisplayError("YardRoute, No turnout entries in route " .
                                    "'$route'.");
            $$YardRouteData{'Control'}{'Route'} = "";
            $$YardRouteData{'Control'}{'Inprogress'} = 0;
         }
      }
      else {
         $$YardRouteData{'Control'}{'Inprogress'} = 0;
      }
   }

   return 0;
}

# =============================================================================
# FUNCTION:  TestSound
#
# DESCRIPTION:
#    This routine is used to select and audition the sound files in the sound 
#    file directory when the -p command line option is specified.
#
# CALLING SYNTAX:
#    $result = &TestSound($SoundDir);
#
# ARGUMENTS:
#    $SoundDir     Directory holding sound files.
#
# RETURNED VALUES:
#    0 = Success,  1 = Error.
#
# ACCESSED GLOBAL VARIABLES:
#    $main::MainRun, $main::SoundPlayer
# =============================================================================
sub TestSound {
   my($SoundDir) = @_;
   my($result, @fileList, $cnt, $file, $key, $resp, $volume);
   my(%select) = ('00' => 'Exit test.');

   $result = &DisplayDebug(1, "TestSound entry ...   SoundDir: $SoundDir   " .
                              "SoundPlayer: $main::SoundPlayer");

   if (-d $SoundDir) {

      # Get wav file names, sort, and build user picklist. 
      @fileList = sort grep { -f } glob "$SoundDir/*.wav";
      $cnt = 1;
      foreach $file (@fileList) {
         $key = $cnt++;
         $key = "0$key" if (length($key) == 1);
         $select{$key} = substr($file, rindex($file, "/")+1);
      }

      #Display list to user and get selection.
      while ($main::MainRun) {
         $result = &DisplayMessage("TestSound, --------------------------------");
         $result = &DisplayMessage("TestSound, Enter file number to audition.  ");
         $result = &DisplayMessage("TestSound, Include ,xxxx to change volume. ");
         $result = &DisplayMessage("TestSound, 400 (loudest), -10000 (softest).");
         foreach $key (sort keys(%select)) {
            $result = &DisplayMessage("TestSound,    $key: $select{$key}");
         }
         $result = &DisplayMessage("TestSound, --------------------------------");
         print "$$ TestSound, Enter selection: ";
         $resp = <>;
         chomp($resp);
         if ($resp =~ m/(\d+),(.+)/) {
            $resp = $1;
            $volume = $2;
         }
         else {
            $volume = '';
         }
         $resp = "0$resp" if (length($resp) == 1);
         return 0 if ($resp eq '00');
         if (exists $select{$resp}) {
            if ($volume ne '') {
               if ($volume <= 400 and $volume >= -10000) {
                  system("/usr/bin/amixer set PCM -- $volume");
                  if (($? >> 8) != 0) {
                     $result = &DisplayError("TestSound, amixer call returned " .
                                             "error. $!");
                  }
               }
            }
            $result = &PlaySound($select{$resp});
            $result = &DisplayMessage("TestSound, playing selection $resp ...");
         }
         else {
            $result = &DisplayError("TestSound, Entry '$resp' not found.");
         }
      }
   }
   else {
      $result = &DisplayError("TestSound, Sound file directory not " .
                              "found: $SoundDir");
      return 1;
   }

   return 0;
}

# =============================================================================
# FUNCTION:  TestRelay
#
# DESCRIPTION:
#    This routine is called by the DnB_main code to test the power polarity 
#    relays when the -r command line option is specified. The relays are 
#    sequentially energized at a one second interval. After a five second 
#    delay, the relays are sequentially de-energized. Following another five
#    second delay, the cycle repeats. This test runs until terminated by
#    ctrl-c, $main::MainRun = 0;. 
#
# CALLING SYNTAX:
#    $result = &TestRelay(\%GpioData);
#
# ARGUMENTS:
#    $GpioData      Pointer to %GpioData hash. (polarity relays) 
#
# RETURNED VALUES:
#    0 = Success,  1 = Error.
#
# ACCESSED GLOBAL VARIABLES:
#    $main::MainRun
# =============================================================================
sub TestRelay {
   my($GpioData) = @_;
   my($result, $gpio, $check, $desc);
   my($value) = 1;

# Run indefinite test loop.

   while ($main::MainRun) {
      foreach $gpio (sort keys(%$GpioData)) {
         if ($gpio =~ m/^GP.+?_PR/) {
            $$GpioData{$gpio}{'Obj'}->write($value);  # Set power polarity relay.
            $check = $$GpioData{$gpio}{'Obj'}->read;  # Readback and check.
            if ($check != $value) {
               $result = &DisplayError("TestRelay, Failed to set $gpio (" .
                                      $$GpioData{$gpio}{'Desc'} . ") to $value");
            }
            else {
               $result = &DisplayMessage("TestRelay, $gpio (" .
                                         $$GpioData{$gpio}{'Desc'} . 
                                         ") set to $value");
            }
            select (undef, undef, undef, .5);   # Delay
         }
      }
      sleep 5;
      $value = (~$value) & 1;     # Compliment the working value.
   }
   return 0;
}

return 1;
