package RFC::PSU_Bipolar;
#$Id: PSU_Bipolar.pm,v 1.34 2017/11/06 07:37:34 sqko Exp $

#######################################################################
#
#
#      RFC::PSU_Bipolar
#
#
# This class defines the behaviour of container PSU modules.
# A PSU_Bipolar instance emulates a true bipolar power supply/eload by
# combining two power supplies or a power supply and an electronic load.
# A power supply can be used as an electronic load if it is fitted with
# DC offset diodes which offset the inherent DC bias of the device under 
# test. In the case of a single fuel cell, this is usually below 2 volt,
# and a series of 2 to 3 silicon diodes can handle the DC bias.
# However beware that the diodes must be able to handle the full DC current 
# load (which potentially could be several hundred amps in case of big PSU 
# units) and thus the diodes likely have to be extensively cooled to awoid
# thermal damage!
#
# A PSU_Bipolar instance behaves exactly as a normal PSU instance, however 
# the minvoltage and mincurrent values are defined by the device acting as
# the PSU and the maxcurrent and maxcurrent values are defined by the eload 
# device.
#
# The voltage() function is also overloaded so that if an additional argument
# is specified besides the voltage to set, only the psu device is set if the
# second (optional) argument matches the string 'psu' and the eload device
# if it does not match. The rationale for this is that both devices may need
# different voltage settings for true bipolar constant current operation to 
# work.
#
# The benefit of using the container class is that it makes it possible to
# use two normal power supplies as a single true bipolar power supply.
#
# Beware, however, that you do not inadvertedly create an infinite recursion if
# more than one container device is used!
# 
# The RFC::PSU_Bipolar class defines whe following extra member functions:
#
# $id->read_volt_psu()     Returns the result of measure_U() for the psu device
# $id->read_volt_eload()   Returns the result of measure_U() for the eload device
# $id->read_curr_psu()     Returns the result of measure_I() for the psu device
# $id->read_curr_eload()   Returns the result of measure_I() for the eload device
# $id->get_volt_psu()     Returns the result of get_U() for the psu device
# $id->get_volt_eload()   Returns the result of get_U() for the eload device
# $id->get_curr_psu()     Returns the result of get_I() for the psu device
# $id->get_curr_eload()   Returns the result of get_I() for the eload device
#
# These four functions are neeeded for accesing the voltage and current of the 
# individual devices.
#
#######################################################################




use strict;
use warnings;
use RFC::Header;
use RFC::BaseDevice;
use RFC::Cache;
our @ISA = qw(RFC::BaseDevice);

BEGIN {
    use Exporter ();
    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
    $VERSION = 1.0;
    %EXPORT_TAGS = (
        'standard'=>[],
                    );
    Exporter::export_ok_tags('standard');
    Exporter::export_tags('standard');
    @ISA = qw(Exporter);
}

sub new()
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'_setup_tags'} = ['mode','PSU_device','Eload_device','control_mode','show_plot'];
    $self->{'_desc'} = {
        'mode' =>'Device control mode',
	'PSU_device' => 'Device for controling current in electrolyser mode (negative current)',
	'Eload_device' => 'Device for controling current in fuel cell mode (positive current)',
        'title' => 'Optional device title',
        'show_plot'=>'Determines if the current device data is to be shown in the daily data plots',
	'control_mode' => 'determines if the set() function should control voltage or current',
    };
    $self->{'_set_fn_names'} = ['current','voltage','RSD']; # List of function names which can trigger a notify event
    $self->{'_type'} = 'PSU_Bipolar';
    $self->{'_default'} = {
        'mode' =>'Automatic',
        'show_plot'=>'Yes',
	'control_mode'=>'Current',
    };
    $self->{'_psu'} = undef;
    $self->{'_eload'} = undef;
    $self->{'_U'} = undef;
    $self->{'_I'} = undef;
    $self->{'_mode'} = 'Automatic';
    $self->{'_values_mode'} = ['Automatic'];
    $self->{'_values_control_mode'} = ['Voltage','Current'];
    $self->{'_values_PSU_device'} = ['',($self->rig->avaliable_devices('PSU'))];
    $self->{'_values_Eload_device'} = ['',($self->rig->avaliable_devices('PSU'))];
    $self->{'_section'} = 'PSU_'.$self->name;
    $self->{'_read_names'} = ['read','measure_I','measure_U','get_I','get_U'];
    $self->{'_set_names'} = ['current','voltage','set','RSD'];
    bless $self,$class;
    return $self->init;
}

sub set
{
    my $self = shift;
    if ($self->{'_control_mode'} && $self->{'_control_mode'} =~ /current/i)
    {
        return $self->current(@_);
    }
    else
    {
        return $self->voltage(@_);
    }
}

sub maxcurrent()
{
    my $self = shift;
    $self->die("Error: Device '".$self->name."', not properly initialised: one or more of the devices in container could not be initialised!") unless ($self->{'_psu'} && $self->{'_eload'});
    return $self->{'_eload'}->maxcurrent;
}

sub mincurrent()
{
    my $self = shift;
    $self->die("Error: Device '".$self->name."', not properly initialised: one or more of the devices in container could not be initialised!") unless ($self->{'_psu'} && $self->{'_eload'});
    return -$self->{'_psu'}->maxcurrent;
}

sub maxvoltage()
{
    my $self = shift;
    $self->die("Error: Device '".$self->name."', not properly initialised: one or more of the devices in container could not be initialised!") unless ($self->{'_psu'} && $self->{'_eload'});
    return $self->{'_psu'}->maxvoltage;
}

sub minvoltage()
{
    my $self = shift;
    $self->die("Error: Device '".$self->name."', not properly initialised: one or more of the devices in container could not be initialised!") unless ($self->{'_psu'} && $self->{'_eload'});
    return $self->{'_eload'}->minvoltage;
}

sub get_version()
{
    my $self = shift;
    ### get the serial-server version ###
    return SocketClient::serial_client($self->{'_tty'},'version');
}

sub test()
{
    my $self = shift;
    return 0 unless ($self->{'_psu'} && $self->{'_psu'}->test); 
    return 0 unless ($self->{'_eload'} && $self->{'_eload'}->test); 
    return 1;
}


sub Accept
{
    my $self = shift;
    my $visitor= shift;
    return undef if ($self->{'_seen_visitors'}{$visitor});
    $self->{'_eload'}->Accept($visitor) if ($self->{'_eload'});
    $self->{'_psu'}->Accept($visitor) if ($self->{'_psu'});
    $visitor->VisitComplex($self);
    $self->{'_seen_visitors'}{$visitor} = 1;
    return undef;
}


sub test_string()
{
    my $self = shift;
    my $string = 'PSU '.$self->name."\n";
    if ($self->{'_eload'})
    {
	$string .= $self->{'_eload'}->test_string(@_)."\n";
    }
    else
    {
	$string .= "Warning: No E-load device found!\n";
    }
    if ($self->{'_psu'})
    {
	$string .= $self->{'_psu'}->test_string(@_)."\n";
    }
    else
    {
	$string .= "Warning: No PSU device found!\n";
    }
    return $string;
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'PSU type: '.$self->type;
    push @arr,$self->{'_eload'}->print_config if ($self->{'_eload'});
    push @arr,$self->{'_psu'}->print_config if ($self->{'_psu'});
    return join (', ',@arr);
}

sub RSD()
{
    my $self = shift;
    my $status = shift;
    $self->die("Error: Device '".$self->name."', not properly initialised: one or more of the devices in container could not be initialised!") unless ($self->{'_psu'} && $self->{'_eload'});
    $self->{'_psu'}->RSD($status);
    $self->{'_eload'}->RSD($status);
    $self->notify('RSD',$status,@_);
}

sub read_volt_psu
{
    my $self = shift;
    return $self->{'_psu'}->measure_U(@_);
}

sub read_volt_eload
{
    my $self = shift;
    return $self->{'_eload'}->measure_U(@_);
}

sub read_curr_psu
{
    my $self = shift;
    return $self->{'_psu'}->measure_I(@_);
}

sub read_curr_eload
{
    my $self = shift;
    return $self->{'_eload'}->measure_I(@_);
}

sub get_volt_psu
{
    my $self = shift;
    return $self->{'_psu'}->get_U(@_);
}

sub get_volt_eload
{
    my $self = shift;
    return $self->{'_eload'}->get_U(@_);
}

sub get_curr_psu
{
    my $self = shift;
    return $self->{'_psu'}->get_I(@_);
}

sub get_curr_eload
{
    my $self = shift;
    return $self->{'_eload'}->get_I(@_);
}



sub voltage()
{
    my $self = shift;
    if (@_)
    {
	my $v = shift;
        $self->{'_U'} = $v;
	$self->die("Error: Device '".$self->name."', not properly initialised: one or more of the devices in container could not be initialised!") unless ($self->{'_psu'} && $self->{'_eload'});
	if ($v =~ /\d/ )
	{
	    if (@_)
	    {
		my $devname = shift;
		if ($devname && $devname =~ /psu/i)
		{
		    $self->{'_psu'}->voltage($v,@_);
		}
		else
		{
		    $self->{'_eload'}->voltage($v,@_);
		}
	    }
	    else
	    {
		$self->{'_eload'}->voltage($v);
		$self->{'_psu'}->voltage($v);
	    }
	}
	else
	{
	    $self->{'_eload'}->voltage('ocv',@_);
	    $self->{'_psu'}->voltage('ocv',@_);
	}	
	$self->notify('voltage',$v,@_);
    }
    return $self->{'_U'};
}

sub current()
{
    my $self = shift;
    if (@_)
    {
	my $c = shift;
	$self->{'_I'} = $c;
	$self->die("Error: Device '".$self->name."', not properly initialised: one or more of the devices in container could not be initialised!") unless ($self->{'_psu'} && $self->{'_eload'});
	if ($c =~ /\d/ )
	{
	    if ($c > 0)
	    {
		$self->{'_eload'}->current($c,@_);
		$self->{'_psu'}->current(0,@_);
	    }
	    elsif ($c < 0)
	    {
		$self->{'_eload'}->current(0,@_);
		$self->{'_psu'}->current(-$c,@_);
	    }
	    else
	    {
		$self->{'_eload'}->current(0,@_);
		$self->{'_psu'}->current(0,@_);    
	    }
	}
	else
	{
	    $self->{'_eload'}->current('ocv',@_);
	    $self->{'_psu'}->current('ocv',@_);
	}
	$self->notify('current',$c,@_);
    }
    return $self->{'_I'};
}

sub measure_U()
{
    my $self = shift;
    $self->die("Error: Device '".$self->name."', not properly initialised: one or more of the devices in container could not be initialised!") unless ($self->{'_psu'} && $self->{'_eload'});
    if (defined($self->{'_I'}) && $self->{'_I'} =~ /\d/ && $self->{'_I'} > 0)
    {
	return $self->{'_eload'}->measure_U(@_);
    }
    else
    {
	return $self->{'_psu'}->measure_U(@_);
    }
}

sub measure_I()
{
    my $self = shift;
    $self->die("Error: Device '".$self->name."', not properly initialised: one or more of the devices in container could not be initialised!") unless ($self->{'_psu'} && $self->{'_eload'});
    my $pi = $self->{'_psu'}->measure_I(@_);
    my $ei = $self->{'_eload'}->measure_I(@_);
    if ($pi && $ei && abs($pi) > abs($ei))
    {
	return -abs($pi);
    }
    else
    {
	return $ei;
    }
}

sub idn()
{
    my $self = shift;
    $self->die("Error: Device '".$self->name."', not properly initialised: one or more of the devices in container could not be initialised!") unless ($self->{'_psu'} && $self->{'_eload'});
    my @arr = ();
    push @arr, $self->{'_psu'}->idn;
    push @arr, $self->{'_eload'}->idn;
    return join("\n",@arr);
}

sub get_I()
{
    my $self = shift;
    $self->{'_I'} = $self->measure_I(@_);
    return $self->{'_I'};
}

sub get_U()
{
    my $self = shift;
    $self->{'_U'} = $self->measure_U(@_);
    return $self->{'_U'};
}

sub read()
{
    my $self = shift;
    return $self->measure_I(@_);
}

sub readstring()
{
    my $self = shift;
    return undef if ($self->readstring_ignore);
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    $self->check_cache($data);
    my $i = $self->get_I($data);
    my $u = $self->get_U($data);
    my @t = $self->get_time();
    my $t = join(':',@t);
    my $res = join(' ',$self->name.'_current',$t,time,$i,$self->name.'_voltage',$t,time,$u);
    $self->writedebug($res) if ($self->debug > 1);
    return $res;
}

sub init
{
    my $self = shift;
    foreach my $tag ($self->setup_tags())
    {
        $self->{'_'.$tag} = $self->get_default($tag);
        my $conf = $self->get_conf($tag);
        $self->{'_'.$tag} = $conf if (defined($conf) && $conf =~ /\w/);
    }
    ## Init devices in container ###
    $self->{'_psu'} = $self->rig->get_device('PSU',$self->{'_PSU_device'});
    unless ($self->{'_psu'})
    {
        ### Implicitly initilaise device ###
	$self->{'_psu'} = $self->rig->init_device('PSU',$self->{'_PSU_device'});
	$self->{'_psu'}->readstring_ignore(1) if ($self->{'_psu'});
    }
    $self->{'_eload'} = $self->rig->get_device('PSU',$self->{'_Eload_device'});
    unless ($self->{'_eload'})
    {
        ### Implicitly initilaise device ###
	$self->{'_eload'} = $self->rig->init_device('PSU',$self->{'_Eload_device'});
	$self->{'_eload'}->readstring_ignore(1) if ($self->{'_eload'});
    }
    return $self;
}


return 1;
