#===========================================================================
#
#                              CommLib.pm
#
# LIBRARY PURPOSE
#   The purpose of this library is to provide routines to handle 
#   serial port configuration and communication for G* Data Module tests.
#
# EXTERNALIZED INTERFACES:
#
# LIMITATIONS:
#
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#  (c) COPYRIGHT 2001 QUALCOMM, Incorporated. All Rights Reserved
#                     QUALCOMM Proprietary
#
#  Export of this technology or software is regulated by the U.S. Government.
#  Diversion contrary to U.S. law prohibited.
#
#  All ideas, data and information contained in or disclosed by
#  this document are confidential and proprietary information of
#  QUALCOMM Incorporated and all rights therein are expressly reserved.
#  By accepting this material the recipient agrees that this material
#  and the information contained therein are held in confidence and in
#  trust and will not be used, copied, reproduced in whole or in part,
#  nor its contents revealed in any manner to others without the express
#  written permission of QUALCOMM Incorporated.
#
#===========================================================================

package CommLib;

use Exporter;
@ISA = qw (Exporter);

@EXPORT = qw
(
  comm_lib_init
  comm_lib_shutdown
  comm_set_config
  comm_get_config
  comm_get_atcmd_handle
  comm_send_command
  comm_get_response
  $COMM_CONFIG_NONE
  $COMM_CONFIG_DATA
  $COMM_CONFIG_CONTROL
  $COMM_CONFIG_BOTH
  $COMM_PORT_DATA
  $COMM_PORT_CONTROL
);

#===========================================================================
#               INCLUDE FILES
#===========================================================================

use Env qw
(
  COM_DATA
  COM_CONTROL
);


use strict;
use FileHandle;

#===========================================================================
#               DEFINITIONS AND DECLARATIONS
#===========================================================================

use subs qw
(
  comm_lib_init
  comm_lib_shutdown
  comm_set_config
  comm_get_config
  comm_get_atcmd_handle
  comm_send_command
  comm_get_response
);

use vars qw
(
  $COMM_CONFIG_NONE
  $COMM_CONFIG_DATA
  $COMM_CONFIG_CONTROL
  $COMM_CONFIG_BOTH
  $COMM_PORT_DATA
  $COMM_PORT_CONTROL
);

# Exported port configuration enum
*COMM_CONFIG_NONE = \0;
*COMM_CONFIG_DATA = \1;
*COMM_CONFIG_CONTROL = \2;
*COMM_CONFIG_BOTH = \3;

# Exported serial port enum
*COMM_PORT_DATA = \1;
*COMM_PORT_CONTROL = \2;

# Port configuration local data
local *DATAPORT;    # File handle to data port
local *CONTROLPORT; # File handle to control port
local *ATCMDPORT;   # File handle to active command port

my $data_binmode = undef;
my $control_binmode = undef;
my $port_config = $COMM_CONFIG_NONE;   # Current port configuration

# General local data
my $debug_level = 0;
my $default_timeout = 250;  # msec
my $cmd_terminator = "\r";
my $resp_terminator = "\n";

#===========================================================================
#               FUNCTION DEFINITIONS
#===========================================================================



sub comm_lib_init
{
  my $dbg = $_[0];

  if (defined $dbg)
  {
    $debug_level = int($dbg);
  }

  # Set default COM port configuration if not specified by
  # environment variables
  print "DATA port" if ($debug_level > 0);

  if (!defined $COM_DATA)
  {
    $COM_DATA = "COM2";
    print("[default]") if ($debug_level > 0);
  }

  print(": $COM_DATA\n") if ($debug_level > 0);
  print("CONTROL port") if ($debug_level > 0);

  if (!defined $COM_CONTROL)
  {
    $COM_CONTROL = "COM4";
    print("[default]") if ($debug_level > 0);
  }

  print(": $COM_CONTROL\n") if ($debug_level > 0);
}

sub comm_lib_shutdown
{
}

sub comm_set_config
{
  my ($new_config, $binmode_requested) = @_;
  my $CLOSE_CONTROL = undef;
  my $OPEN_CONTROL = undef;
  my $CLOSE_DATA = undef;
  my $OPEN_DATA = undef;

  # Determine port open/close operations required
  if ($new_config == $COMM_CONFIG_DATA)
  {
    # Data port active configuration
    *ATCMDPORT = *DATAPORT;

    if (defined($data_binmode) && !defined($binmode_requested))
    {
      # Only way to reset binmode on a file handle is to close and reopen
      $CLOSE_DATA = 1;
      $OPEN_DATA = 1;
    }
    elsif ($port_config == $COMM_CONFIG_NONE || $port_config == $COMM_CONFIG_CONTROL)
    {
      $OPEN_DATA = 1;
    }

    if ($port_config == $COMM_CONFIG_BOTH || $port_config == $COMM_CONFIG_CONTROL)
    {
      $CLOSE_CONTROL = int(rand 2);
    }
  }
  elsif ($new_config == $COMM_CONFIG_CONTROL)
  {
    # Control port active configuration
    *ATCMDPORT = *CONTROLPORT;

    if (defined($control_binmode) && !defined($binmode_requested))
    {
      # Only way to reset binmode on a file handle is to close and reopen
      $CLOSE_CONTROL = int(rand 2);
      $OPEN_CONTROL = $CLOSE_CONTROL;
    }
    elsif ($port_config == $COMM_CONFIG_NONE || $port_config == $COMM_CONFIG_DATA)
    {
      $OPEN_CONTROL = int(rand 2);
    }

    if ($port_config == $COMM_CONFIG_BOTH || $port_config == $COMM_CONFIG_DATA)
    {
      $CLOSE_DATA = 1;
    }
  }
  elsif ($new_config == $COMM_CONFIG_BOTH)
  {
    # Data port and control port active configuration.
    # Control port is AT commmand port.
    *ATCMDPORT = *CONTROLPORT;

    if (defined($control_binmode) && !defined($binmode_requested))
    {
      # Only way to reset binmode on a file handle is to close and reopen
      $CLOSE_CONTROL = int(rand 2);
      $OPEN_CONTROL = $CLOSE_CONTROL;
    }
    elsif ($port_config == $COMM_CONFIG_NONE || $port_config == $COMM_CONFIG_DATA)
    {
      $OPEN_CONTROL = int(rand 2);
    }

    if ($port_config == $COMM_CONFIG_NONE || $port_config == $COMM_CONFIG_CONTROL)
    {
      $OPEN_DATA = 1;
    }
  }
  elsif ($new_config == $COMM_CONFIG_NONE)
  {
    # No port active configuration
    *ATCMDPORT = undef;

    if ($port_config == $COMM_CONFIG_BOTH || $port_config == $COMM_CONFIG_DATA)
    {
      $CLOSE_DATA = 1;
    }

    if ($port_config == $COMM_CONFIG_BOTH || $port_config == $COMM_CONFIG_CONTROL)
    {
      $CLOSE_CONTROL = int(rand 2);
    }
  }
  else
  {
    die "bad port config\n";
  }

  # If closing control port and doing it first
  if (defined($CLOSE_CONTROL) && $CLOSE_CONTROL == 0)
  {
    print("close $COM_CONTROL\n");
    close(CONTROLPORT);
    *CONTROLPORT = undef;
    $control_binmode = undef;
  }

  # If opening control port and doing it first
  if (defined($OPEN_CONTROL) && $OPEN_CONTROL == 0)
  {
    print("open $COM_CONTROL\n");
    sysopen CONTROLPORT, $COM_CONTROL, O_RDWR, 0666 or die "$COM_CONTROL Open Failed\n";
  }

  # If closing data port
  if (defined($CLOSE_DATA))
  {
    print("close $COM_DATA\n");
    close(DATAPORT);
    *DATAPORT = undef;
    $data_binmode = undef;
  }

  # If opening data port
  if (defined($OPEN_DATA))
  {
    print("open $COM_DATA\n");
    sysopen DATAPORT, $COM_DATA, O_RDWR, 0666 or die "$COM_DATA Open Failed\n";
  }

  # If closing control port and doing it last
  if (defined($CLOSE_CONTROL) && $CLOSE_CONTROL == 1)
  {
    print("close $COM_CONTROL\n");
    close(CONTROLPORT);
    *CONTROLPORT = undef;
  }

  # If opening control port and doing it last
  if (defined($OPEN_CONTROL) && $OPEN_CONTROL == 1)
  {
    print("open $COM_CONTROL\n");
    sysopen CONTROLPORT, $COM_CONTROL, O_RDWR, 0666 or die "$COM_CONTROL Open Failed\n";
  }

  # Enforce minimum time between DTR assertion and data
  # transmission of 5 msec
  if (defined($OPEN_DATA) || defined($OPEN_CONTROL) ||
      defined($CLOSE_DATA) || defined($CLOSE_CONTROL))
  {
    sleep 1;     # This is too long but ok for testing purposes.
  }

  $port_config = $new_config;

  if (($port_config == $COMM_CONFIG_DATA) ||
      ($port_config == $COMM_CONFIG_CONTROL) ||
      ($port_config == $COMM_CONFIG_BOTH))
  {
    # Flush current command port
    ATCMDPORT->autoflush(1);

    # Setting 'binary mode' allows us to get from the command     
    # port buffer exactly what arrived (no <CR><LF> => <LF>). 
    if (defined($binmode_requested))
    {
      if ($port_config == $COMM_CONFIG_DATA)
      {
        print("$COM_DATA set binary\n");
        $data_binmode = 1;
      }
      else
      {
        print("$COM_CONTROL set binary\n");
        $control_binmode = 1;
      }
      binmode ATCMDPORT;
    }
  }

  return 1;
}

sub comm_get_config
{
  return $port_config;
}

sub comm_get_atcmd_handle
{
  return \*ATCMDPORT;
}

sub comm_send_command
{
  my $cmd_str = $_[0];
  my $port = $_[1];
  my $fh;
  my $port_binmode;

  # Add command string terminator
  $cmd_str .= $cmd_terminator;

  # Determine the serial port on which to transmit
  if (!defined $port)
  {
    # Default port is the current AT command port
    $fh = \*ATCMDPORT;

    if ($port_config == $COMM_CONFIG_DATA)
    {
      $port_binmode = $data_binmode;
    }
    else
    {
      $port_binmode = $control_binmode;
    }
  }
  else
  {
    if ($port eq $COMM_PORT_DATA)
    {
      $fh = \*DATAPORT;
      $port_binmode = $data_binmode;
    }
    elsif ($port eq $COMM_PORT_CONTROL)
    {
      $fh = \*CONTROLPORT;
      $port_binmode = $control_binmode;
    }
    else
    {
      $fh = undef;
      $port_binmode = undef;
    }
  }

  if (syswrite($fh, $cmd_str, length($cmd_str)) != length($cmd_str))
  {
    die "Could not write all data to serial port\n";
  }

  # If the port is in binary mode, the echoed command line can be retrieved.
  # Otherwise, we'll hang for quite a while waiting for the port idle timeout
  # since a LF is not received, only a CR.
  if (defined($port_binmode))
  {
    my $resp_str;
    my $len;
#   print "reading echoed chars...";
    if (sysread($fh, $resp_str, length($cmd_str)) != length($cmd_str))
    {
      print "received fewer bytes than expected: $resp_str\n";
      return 0;
    }
    elsif ($resp_str eq $cmd_str)
    {
#     print "OK\n";
    }
    else
    {
      print "error: $resp_str\n";
      return 0;
    }
  }

  return 1;
}

sub comm_get_response
{
  my $port = $_[0];

  my $fh;
  my $len;
  my $resp_str;

  # Determine the serial port from which to read
  if (!defined $port)
  {
    # Default port is the current AT command port
    $fh = \*ATCMDPORT;
  }
  else
  {
    if ($port eq $COMM_PORT_DATA)
    {
      $fh = \*DATAPORT;
    }
    elsif ($port eq $COMM_PORT_CONTROL)
    {
      $fh = \*CONTROLPORT;
    }
    else
    {
      *COMPORT = undef;
    }
  }

  $len = sysread($fh, $resp_str, 2048);

  if ($len <= 0 || !defined($resp_str))
  {
    # sysread() is a blocking call, so it's really not possible for it
    # to return without data, but handle it anyway.
    print "comm: response not received\n";
    return undef;
  }

  return $resp_str;
}

1;

