package RFC::PSUMulti;
#$Id: PSUMulti.pm,v 1.17 2016/06/13 12:22:58 sqko Exp $
#######################################################################
#
#
#      RFC::PSUMulti
#
#
# This class defines the behaviour of parallel connected PSU devices
# with diferent ranges. This is intended to be used in the case where 
# both high currents and high presision at low currents are needed (and 
# which rarely is possible with a single PSU). 
# A RFC::PSUMulti class instance behaves as  a normal PSU device with the 
# only exception that it is not possible to use parallel or serial 
# connections of RFC::PSUMulti devices (this has to be done to the 
# individual RFC::PSU devices).
#
# As the API of RFC::PSUMulti is identical to RFC::PSU, it is possible
# to have more than 2 PSU devices controlled in this way (as a RFC::PSUMulti.
# device can contain RFC::PSUMulti as well as RFC::PSU device instances).
#
# Beware that you do not inadvertedly create an infinite recursion if
# more than one container device is used!
#
# It is assumed that the individual PSU devices used handle 'OCV' commands
# correctly (that is that the PSU is placed in a high impedance output
# state either by PSU design or by an external relay device, refer RFC::PSU
# for details).
#
#######################################################################
use strict;
use warnings;
use RFC::Header;
use RFC::PSU;
use RFC::Cache;
our @ISA = qw(RFC::PSU);


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->{'_type'} = 'PSUMulti';
    $self->{'_setup_tags_readonly'} = ['mode','minvoltage','maxvoltage','mincurrent','maxcurrent','cutoff_set'];
    $self->{'_setup_tags'} = ['class','mode','device_1','device_2','cutoff_device_1','cutoff_device_2','minvoltage','maxvoltage','mincurrent','maxcurrent','shift_up','shift_down','steps','sleep','control_mode','master_device','slave_current','slave_voltage','show_plot'];
    $self->{'_desc'} = {
        'mode' => 'Device control mode',
        'class' => 'Device type',
	'device_1' => 'Name of low range PSU device',
	'device_2' => 'Name of high range PSU device',
	'cutoff_device_1' => 'curent for device 1 below which the current is defined to be 0 (OCV)',
	'cutoff_device_2' => 'curent for device 2 below which the current is defined to be 0 (OCV)',
        'steps' => 'Number of steps in current shift between devices',
        'sleep' => 'Wait time in seconds on each step in a shift between devices',
        'shift_up' => 'Flow level where control shifts from small to large device, default is maxcurrent of low range device',
        'shift_down' => 'Flow level where control shifts from large to small device, default is 80 percent of maxcurrent of low range device',
        'title' => 'Optional device title',
        'master_device' => 'Device name for the master device in case current device is a slave device (locked to have X percent of master device current)',
        'slave_current' => 'Percentage of master device current that this device is supposed to have',
        'slave_voltage' => 'Percentage of master device voltage that this device is supposed to have',
        'control_mode' => 'determines if the set() function should control voltage or current',
        'show_plot'=>'Determines if the current device data is to be shown in the daily data plots',
    };
    $self->{'_default'} = {
        'class' => 'PSUMulti',
        'mode' => 'Automatic',
        'steps' => 1,
        'sleep' => 1,
        'shift_up' => 1,
        'shift_down' => 0.8,
	'cutoff_device_1' => 0,
	'cutoff_device_2' => 0,
        'slave_current' => 100,
        'slave_voltage' => 100,
        'control_mode'=>'Current',
    };
    $self->{'_set_fn_names'} = ['curent','voltage']; # List of function names which can trigger a notify event
    $self->{'_values_mode'} = ['Automatic'];
    $self->{'_values_class'} = ['Normal','PSUMulti'];
    $self->{'_values_control_mode'} = ['Current'];
    $self->{'_U'} = undef;
    $self->{'_I'} = undef;
    $self->{'_dev1'} = undef;
    $self->{'_dev2'} = undef;
    bless $self,$class;
    my @list = ();
    foreach my $n ($self->rig->avaliable_devices('PSU'))
    {
	next unless ($n);
        next if ($n eq $self->name);
        push @list,$n;
    }
    $self->{'_values_device_1'} = ['',@list];
    $self->{'_values_device_2'} = ['',@list];
    @list = ();
    foreach my $n ($self->rig->avaliable_devices('PSU'))
    {
	next unless ($n);
        push @list,$n unless ($n eq $self->name);
    }
    $self->{'_values_master_device'} = ['',@list];
    ### Remove reduddant inherited keys ###
    foreach ('RSD_device','tty','RSD_type','values_RSD_type','RSD_address','values_external_RSD','values_RSD_name','RSD_tty','external_RSD','RSD_name','RSD_channel','serial','parallel')
    {
	delete($self->{'_'.$_});
    }
    return $self->init();
}

sub readonly_value($)
{
    my $self = shift;
    my $tag = shift;
    return undef unless ($tag);
    return $self->mode() if ($tag eq 'mode');
    return $self->maxcurrent() if ($tag eq 'maxcurrent');
    return $self->mincurrent() if ($tag eq 'mincurrent');
    return $self->maxvoltage() if ($tag eq 'maxvoltage');
    return $self->minvoltage() if ($tag eq 'minvoltage');
    return undef;
}



sub maxcurrent()
{
    my $self = shift;
    return $self->{'_maxI'};
}

sub mincurrent()
{
    my $self = shift;
    return $self->{'_minI'};
}

sub maxvoltage()
{
    my $self = shift;
    return $self->{'_maxU'};
}

sub minvoltage()
{
    my $self = shift;
    return $self->{'_minU'};
}


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 ###
    my $d1 = $self->rig->get_device('PSU',$self->{'_device_1'});
    my $d2 = $self->rig->get_device('PSU',$self->{'_device_2'});
    unless ($d1)
    {
        ### Implicitly initilaise device d1 ###
        $d1 = $self->rig->init_device('PSU',$self->{'_device_1'});
        $d1->readstring_ignore(1) if ($d1);
    }
    unless ($d2)
    {
        ### Implicitly initilaise device d2 ###
        $d2 = $self->rig->init_device('PSU',$self->{'_device_2'});
        $d2->readstring_ignore(1) if ($d2);
    }
    if ($d1 && $d2)
    {
        ### Class invariant: Device 1 has lower maxcurrent than device 2 ###
        if ($d1->maxcurrent < $d2->maxcurrent)
        {
            $self->{'_dev1'} = $d1;
            $self->{'_dev2'} = $d2;     
        }
        else
        {
	    $self->die("Error: Device 2 must have the high current range!");
        } 
        $self->{'_mode'} = 'Automatic';
    }
    if (!$self->{'_dev1'} || $self->get_conf('gas_'.$self->name,'shift_up'))
    {
        $self->{'_shift_up'} = $self->get_default('shift_up');
        my $conf = $self->get_conf('shift_up');
        $self->{'_shift_up'} = $conf if (defined($conf) && $conf =~ /\w/);
    }
    else
    {
        $self->{'_shift_up'} = $self->{'_dev1'}->maxcurrent();
        $self->{'_default'}{'shift_up'} = $self->{'_dev1'}->maxcurrent();
    }
    if (!$self->{'_dev1'} || $self->get_conf('gas_'.$self->name,'shift_down'))
    {
        $self->{'_shift_down'} = $self->get_default('shift_down');
        my $conf = $self->get_conf('shift_down');
        $self->{'_shift_down'} = $conf if (defined($conf) && $conf =~ /\w/);
    }
    else
    {
        $self->{'_shift_down'} = $self->{'_dev1'}->maxcurrent() * 0.8;
    }    
    if ($self->{'_dev1'})
    {
        $self->{'_default'}{'shift_up'} = $self->{'_dev1'}->maxcurrent();
        $self->{'_default'}{'shift_down'} = $self->{'_dev1'}->maxcurrent() * 0.8;
    }
    $self->{'_section'} = 'PSU_'.$self->name;
    my $m1 = 0;
    my $m2 = 0;
    $m1 = $self->{'_dev1'}->maxcurrent if ($self->{'_dev1'});
    $m2 = $self->{'_dev2'}->maxcurrent if ($self->{'_dev2'});
    $self->{'_maxI'} = $m1 + $m2;
    $m1 = 0;
    $m2 = 0;
    $m1 = $self->{'_dev1'}->mincurrent if ($self->{'_dev1'});
    $m2 = $self->{'_dev2'}->mincurrent if ($self->{'_dev2'});
    $self->{'_minI'} = $m1 + $m2;
    $m1 = 0;
    $m2 = 0;
    $m1 = $self->{'_dev1'}->minvoltage if ($self->{'_dev1'});
    $m2 = $self->{'_dev2'}->minvoltage if ($self->{'_dev2'});
    $self->{'_minU'} = ($m1>$m2?$m1:$m2);
    $m1 = 0;
    $m2 = 0;
    $m1 = $self->{'_dev1'}->maxvoltage if ($self->{'_dev1'});
    $m2 = $self->{'_dev2'}->maxvoltage if ($self->{'_dev2'});
    $self->{'_maxU'} = ($m1<$m2?$m1:$m2);
    $self->{'_mdev'} = undef;
    if ($self->{'_master_device'})
    {
        my $dev = $self->rig->init_device('PSU',$self->{'_master_device'});
        $self->die("Could not get a device instance for master device '".$self->{'_master_device'}."'!") unless ($dev);
        $self->die("A device can not be slave to an other slave device!") if ($dev->master);
        $self->{'_mdev'} = $dev;
        $dev->attatch_observer(\$self);
        $self->{'_readonly'} = 1; ### A slave device can not  be directly controlled, only indirectly ###
    }
    return $self;
}

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

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


sub test_string()
{
    my $self = shift;
    my $string = 'PSU '.$self->name.' current: '.$self->read(@_)."\n";
    $string .= 'Device 1: '.$self->{'_dev1'}->test_string(@_)."\n" if ($self->{'_dev1'});
    $string .= 'Device 2: '.$self->{'_dev2'}->test_string(@_)."\n" if ($self->{'_dev2'});
    return $string;
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'PSU type: '.$self->type;
    push @arr,'Address: '.$self->{'_address'};
    if ($self->{'_dev1'})
    {
	push @arr,'Device_1_maxcurrent: '.$self->{'_dev1'}->maxcurrent;
	push @arr,$self->{'_dev1'}->print_config;
    }
    else
    {
	push @arr,'Device_1: NO_DEVICE!';
    }
    if ($self->{'_dev2'})
    {
	push @arr,'Device_2_maxcurrent: '.$self->{'_dev2'}->maxcurrent;
	push @arr,$self->{'_dev2'}->print_config;
    }
    else
    {
	push @arr,'Device_2: NO_DEVICE!';
    }
    return join (', ',@arr);
}

sub read()
{
    my $self = shift;
    $self->die("Could not read curent for device '".$self->name."', one or more of the devices in container could not be initialised!") unless ($self->{'_dev1'} && $self->{'_dev2'});
    my $res = undef;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    $self->check_cache($data);
    my $c1 = $self->{'_dev1'}->read($data);
    my $c2 = $self->{'_dev2'}->read($data);
    $c1 = 0 if (abs($c1) < $self->{'_cutoff_device_1'});
    $c2 = 0 if (abs($c2) < $self->{'_cutoff_device_2'});
    my $current = $c1 + $c2;
    $self->writedebug('Current for PSU device '.$self->name()." was $current A");
    my $str = time .' '.$self->name.' '.$current;
    $self->rig->kanaldata($str) unless ($self->readstring_ignore);
    return $current;
}

sub measure_I
{
    my $self = shift;
    $self->die("Could not read curent for device '".$self->name."', one or more of the devices in container could not be initialised!") unless ($self->{'_dev1'} && $self->{'_dev2'});
    my $res = undef;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    $self->check_cache($data);
    my $cached = $data->get('PSU',$self->{'_name'},'I');
    return $cached if (defined($cached));
    my $c1 = $self->{'_dev1'}->measure_I($data);
    my $c2 = $self->{'_dev2'}->measure_I($data);
    $c1 = 0 if (abs($c1) < $self->{'_cutoff_device_1'});
    $c2 = 0 if (abs($c2) < $self->{'_cutoff_device_2'});
    my $current = $c1 + $c2;
    $data->set('PSU',$self->{'_name'},'I',$current);
    return $current;
}

sub measure_U
{
    my $self = shift;
    $self->die("Could not read curent for device '".$self->name."', one or more of the devices in container could not be initialised!") unless ($self->{'_dev1'} && $self->{'_dev2'});
    my $res = undef;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    $self->check_cache($data);
    my $cached = $data->get('PSU',$self->{'_name'},'U');
    return $cached if (defined($cached));
    my $c1 = $self->{'_dev1'}->measure_I($data);
    my $u1 = $self->{'_dev1'}->measure_U($data);
    my $u2 = $self->{'_dev2'}->measure_U($data);
    $c1 = 0 if (abs($c1) < $self->{'_cutoff_device_1'});
    my $voltage = ($c1 == 0 ? $u2 : $u1);
    $data->set('PSU',$self->{'_name'},'U',$voltage);
    return $voltage;
}

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

sub idn()
{
    my $self = shift;
    return $self->{'_dev1'}->idn(). ' '. $self->{'_dev2'}->idn();
}


sub voltage
{
    my $self = shift;
    if (@_)
    {
        $self->{'_U'} = 'ocv';
        my $c = shift;
        if ($c =~ /([\d\.]+)/)
        {
            $self->{'_U'} = $1;
            $self->{'_U'} = $self->{'_minU'} if ($self->{'_U'} < $self->{'_minU'});
            $self->{'_U'} = $self->{'_maxU'} if ($self->{'_U'} > $self->{'_maxU'});
        }
	$self->{'_dev1'}->voltage($c);
	$self->{'_dev2'}->voltage($c);
	$self->proglog("Voltage for PSU ".$self->name." set to ".$self->{'_U'}." V",@_);
        $self->notify('voltage',$c,@_);
    }
    return $self->get_U();
}


sub current
{
    my $self = shift;
    my $data = RFC::Cache->new();
    if (@_)
    {
        $self->{'_I'} = 'ocv';
        my $c = shift;
        if ($c =~ /([\d\.]+)/)
        {
            $self->{'_I'} = $1;
            $self->{'_I'} = $self->{'_minI'} if ($self->{'_I'} < $self->{'_minI'});
            $self->{'_I'} = $self->{'_maxI'} if ($self->{'_I'} > $self->{'_maxI'});
        }
        if ($self->{'_I'} =~ /ocv/i)
        {
            $self->writedebug('Setting current to OCV') if ($self->debug > 1);
            $self->{'_dev1'}->current('ocv');
            $self->{'_dev2'}->current('ocv');
	    $self->proglog("Current for PSU ".$self->name." set to ".$self->{'_I'}." A",@_);
	    $self->notify('current',$c,@_);
	    return $self->get_I();
        }
        else
        {
	    if ($c > $self->maxcurrent)
	    {
		$self->writedebug("Flow to be set for ".$self->name." was above maximum, setting maximum current");
		$self->proglog("Flow to be set for ".$self->name." was above maximum, setting maximum current",@_);
		$c = $self->maxcurrent;
		$self->{'_I'} = $c;
	    }
	    $self->proglog("Current for PSU ".$self->name." set to ".$self->{'_I'}." A",@_);
	    $self->notify('current',$c,@_);
	    my $oldc = $self->read($data);
	    my $dev1_c = $self->{'_dev1'}->read($data);
	    my $dev2_c = $self->{'_dev2'}->read($data);
            $self->writedebug('Setting current to '.$self->{'_I'}.' A') if ($self->debug > 1);
	    if ($c > $self->{'_dev2'}->maxcurrent)
	    {
		### Handle current above maximum for high range PSU ###
		if (($c - $self->{'_dev2'}->maxcurrent()) > $self->{'_cutoff_device_1'})
		{
		    $self->{'_dev2'}->current($self->{'_dev2'}->maxcurrent,@_);
		    $self->{'_dev1'}->current($c - $self->{'_dev2'}->maxcurrent(),@_);
		}
		else
		{
		    ### Make sure that the low flow device's cutoff_set does not ###
		    ### result in wrong current                                     ###
		    $self->{'_dev2'}->current($c - $self->{'_cutoff_device_1'},@_);
		    $self->{'_dev1'}->current($self->{'_cutoff_device_1'},@_);           
		}
		return $c;
	    }
	    if ($oldc < $self->{'_cutoff_device_1'})
	    {

		if ($c > $self->{'_shift_up'})
		{
		    $self->{'_dev1'}->current('ocv',@_);
		    return $self->{'_dev2'}->current($c,@_);		    
		}
		else
		{
		    $self->{'_dev2'}->current('ocv',@_);
		    return $self->{'_dev1'}->current($c,@_);
		}
	    }
	    if ($oldc <= $self->{'_shift_up'} && $c > $self->{'_shift_up'})
	    {
		if ($dev1_c > $self->{'_cutoff_device_1'})
		{
		    ### Shift flow from dev 1 to dev2 ###
		    $self->shift_dev($self->{'_dev1'},$self->{'_dev2'},$oldc,@_);
		}
		$self->{'_dev1'}->current('ocv',@_);
		return $self->{'_dev2'}->current($c,@_);
	    }
	    if ($oldc >= $self->{'_shift_down'} && $c < $self->{'_shift_down'})
	    {
		my $dev1max = $self->{'_dev1'}->maxcurrent;
		if ($dev2_c > $dev1max)
		{
		    $self->{'_dev2'}->current($dev1max,@_);
		    $self->{'_dev1'}->current('ocv',@_);
		    sleep($self->{'_sleep'});
		    $oldc = $dev1max;
		}
		if ($dev2_c > $self->{'_cutoff_device_2'})
		{
		    ### Shift flow from dev 2 to dev1 ###
		    $self->shift_dev($self->{'_dev2'},$self->{'_dev1'},$oldc,@_);
		}
		$self->{'_dev2'}->curent('ocv',@_);
		return $self->{'_dev1'}->curent($c,@_);
	    }
	    if ($dev2_c > $self->{'_cutoff_device_2'})
	    {
		$self->{'_dev1'}->current('ocv',@_);
		return $self->{'_dev2'}->current($c,@_);
	    }
	    else
	    {
		$self->{'_dev2'}->current('ocv',@_);
		return $self->{'_dev1'}->current($c,@_);
	    }   
	}
    }
    else
    {
        return $self->get_I();
    }
}


sub shift_dev($$)
{
    my $self = shift;
    my $d1 = shift;
    my $d2 = shift;
    my $flow = shift;
    my $n = $self->{'_steps'};
    for (my $x = 1; $x <= $n; $x++)
    {
        my $f1 = $flow * ( $n - $x ) / $n;
        my $f2 = $flow * $x  / $n;
        $d1->current($f1,@_);
        $d2->current($f2,@_);
        sleep($self->{'_sleep'});
    }
}


return 1;
