package SocketClient;
#$Id: SocketClient.pm,v 1.39 2023/05/30 10:36:55 sqko Exp $
use strict;
use warnings;
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
use Debug;
use IO::Socket::INET;
use IO::Interface::Simple;

#####################################################################################
#
#
#  Package SocketClient
#
# This module defines a number of communication functions used for accessing tcp:IP 
# sockets on local and/or remote systems. The functions defined are listed below:
#
# -- \begin{longtable}{l p{10cm}}
# -- socket_client_raw($$@) & Base function used by all subsequent functions, handles the raw tcp:IP cummunication. Arguemnts: server, port, [additional args to server]. The server can either be a ip-address or a hostname. Any additional arguments gets serialised with tab characters and 2 newlines are appended to the resulting string before transmission. \\
# -- socket_client($$@) & Same as above, but catches any communication errors in an eval guard. \\
# -- socket_client_nocr($$@) & Same as above, but do not append any newlines to the transmitted string. \\
# -- socket_client_raw_nocr($$@) & same as socket_client_raw() but do not append newlines. \\
# -- serial_client($@) & communicates with a local serial server (which handles hardware communication on the serial port. Arguments: tty, args_to_server. The server is assumed to be the local server (either localhost or the public IP address of the server) and the port number is the tty number added to 202020 (Note wraparound!). \\
# -- GPIB_client() & Communicates with the GPIB-server. Arguments are passed to the GPIB-server serialised with tab characters using socket_client_nocr(). The server is assumed to be the local server (either localhost or the public IP address of the server) and the port number is 12345. \\
# -- serial_client_raw($@) &  Same as serial_client() but without eval guard. \\
# -- GPIB_client_raw() & Same as GPIB_client() but without eval guard. \\
# -- timeout([$]) & Sets or gets the timeout value (default is 2 seconds). \\
# -- \end{longtable}
#
#####################################################################################


BEGIN {
    use Exporter ();
    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
    $VERSION = '6.0.0';
    @EXPORT_OK = qw(serial_client serial_client_raw socket_client socket_client_raw GPIB_client GPIB_client_raw timeout);
    %EXPORT_TAGS = (
        'standard'=>[qw/socket_client GPIB_client serial_client socket_client_raw GPIB_client_raw serial_client_raw/]
                    );
    Exporter::export_ok_tags('standard');
    Exporter::export_tags('standard');
    @ISA = qw(Exporter);
}

my $timeout = 2;
my $deb = Debug->new();

### Speckproc for testing if linux_gpib gpibserver is in use and if so, use gpib_client with newline
my $linux_gpib = 0;
$linux_gpib = 1 if (-e '/etc/gpib/linux_gpib');


our $remote = undef;
#### Try to find external ip address of network interface ###
my @ilst = ('eth','em','en','lo');
my @ifl = IO::Interface::Simple->interfaces();
 OUTER:
    foreach my $try (@ilst)
{
    foreach my $interface (@ifl)
    {
	if ($interface =~ /$try/)
	{
	    my $if = IO::Interface::Simple->new($interface);
	    $remote = $if->address();
	    last OUTER if ($remote);
	}
    }
}
### Use localhost if anything else fails ###
$remote  = 'localhost' unless ($remote);

return 1;

sub timeout
{
    if (@_)
    {
	$timeout = shift;
    }
    return $timeout;
}

sub socket_client($$@)
{
    my ($remote,$port, $iaddr, $paddr, $proto, $line);
    $remote  =  shift;
    $port    =  shift;
    if ($port =~ /\D/) {
	$port = getservbyname($port, 'tcp') 
    }
    return undef unless $port;
    my $out = '';
    eval {
	my $socket = new IO::Socket::INET (PeerAddr => $remote,
					   PeerPort => $port,
					   Proto    => "tcp",
					   Timeout => $timeout,
					   Type     => SOCK_STREAM)
	    or die "Can't connect to $remote:$port : $!\n";
	my @args = ();
	foreach my $item (@_)
	{
	    push @args,$item if defined($item);
	}
	print $socket join("\t",@args);
	print $socket "\n\n";
	while (defined($line = <$socket>))
	{
	    $out = $out.$line;
	}
    };
    if ($@)
    {
	my $err = $@;
	chomp $err;
	$deb->writedebug($err,5);
	if ($out)
	{
	    $out .= ' ERROR: '.$err;
	}
	else
	{
	    $out = 'ERROR: '.$err;
	}
    }
    return $out;
}

sub socket_client_raw($$@)
{
    my ($remote,$port, $iaddr, $paddr, $proto, $line);
    $remote  =  shift;
    $port    =  shift;
    if ($port =~ /\D/) {
	$port = getservbyname($port, 'tcp'); 
    }
    return undef unless $port;
    my $out = '';
    my $socket = new IO::Socket::INET (PeerAddr => $remote,
				       PeerPort => $port,
				       Proto    => "tcp",
				       Timeout => $timeout,
				       Type     => SOCK_STREAM)
	or die "Can't connect to $remote:$port : $!\n";
    my @args = ();
    foreach my $item (@_)
    {
	push @args,$item if defined($item);
    }
    print $socket join("\t",@args);
    print $socket "\n\n";
    while (defined($line = <$socket>))
    {
	$out = $out.$line;
    }
    return $out;
}

sub socket_client_nocr($$@)
{
    my ($remote,$port, $iaddr, $paddr, $proto, $line);
    $remote  =  shift;
    $port    =  shift;
    if ($port =~ /\D/) 
    {
	$port = getservbyname($port, 'tcp');
    }
    return undef unless $port;
    my $out = '';
    eval {
	my $socket = new IO::Socket::INET (PeerAddr => $remote,
					   PeerPort => $port,
					   Proto    => "tcp",
					   Timeout => $timeout,
					   Type     => SOCK_STREAM)
	    or die "Can't connect to $remote:$port : $!\n";
	my @args = ();
	foreach my $item (@_)
	{
	    push @args,$item if defined($item);
	}
	print $socket join("\t",@args);
	print $socket "\n\n";
	while (defined($line = <$socket>))
	{
	    $out = $out.$line;
	}
    };
    if ($@)
    {
	my $err = $@;
	chomp $err;
	$deb->writedebug($err,5);
	if ($out)
	{
	    $out .= ' ERROR: '.$err;
	}
	else
	{
	    $out = 'ERROR: '.$err;
	}
    }
    return $out;
}

sub socket_client_raw_nocr($$@)
{
    my ($remote,$port, $iaddr, $paddr, $proto, $line);
    $remote  =  shift;
    $port    =  shift;
    if ($port =~ /\D/) 
    {
	$port = getservbyname($port, 'tcp'); 
    }
    return undef unless $port;
    my $out = '';
    my $socket = new IO::Socket::INET (PeerAddr => $remote,
				       PeerPort => $port,
				       Proto    => "tcp",
				       Timeout => $timeout,
				       Type     => SOCK_STREAM)
	or die "Can't connect to $remote:$port : $!\n";
    my @args = ();
    foreach my $item (@_)
    {
	push @args,$item if defined($item);
    }
    print $socket join("\t",@args);
    print $socket "\n\n";
    while (defined($line = <$socket>))
    {
	$out = $out.$line;
    }
    return $out;
}

sub serial_client($@)
{
    my $port = 202020;
    my $lp = shift;
    die "No port found: $lp\n" unless (defined($lp));
    $lp =~ /(\d+)/;
    $port += $1;
    $port = $port & 0xffff;
    my $res = undef;
    eval
    {
	$res = socket_client_raw('localhost',$port,@_);
    };
    if ($@)
    {
	$res = socket_client_raw($remote,$port,@_);
    }
    return $res;
}

sub serial_client_raw($@)
{
    my $port = 202020;
    my $lp = shift;
    die "No port found: $lp\n" unless (defined($lp));
    $lp =~ /(\d+)/;
    $port += $1;
    $port = $port & 0xffff;
    my $res = undef;
    eval
    {
	$res = socket_client_raw('localhost',$port,@_);
    };
    if ($@)
    {
	$res = socket_client_raw($remote,$port,@_);
    }
    return $res;
}


sub GPIB_client(@)
{
    my $port = 12345;
    my $res = undef;
    eval
    {
        if ($linux_gpib)
        {
	    $res = socket_client_raw('localhost',$port,@_);
        }
        else
        {
            $res = socket_client_raw_nocr('localhost',$port,@_);
        }
    };
    if ($@)
    {
        if ($linux_gpib)
        {
            $res = socket_client_raw($remote,$port,@_);
        }
        else
        {
	    $res = socket_client_raw_nocr($remote,$port,@_);
	}
    }
    return $res;
}


sub GPIB_client_raw(@)
{
    my $port = 12345;
    my $res = undef;
    eval
    {
        if ($linux_gpib)
        {
            $res = socket_client_raw($remote,$port,@_);
	}
        else
	{
            $res = socket_client_raw_nocr($remote,$port,@_);
	}
    };
    if ($@)
    {
	if ($linux_gpib)
	{
            $res = socket_client_raw('localhost',$port,@_);
	}
        else
	{
            $res = socket_client_raw_nocr('localhost',$port,@_);
	}
    }
    return $res;
}
