package RFC::RFCPID;
#$Id: RFCPID.pm,v 1.102 2023/11/22 13:47:46 sqko Exp $
#################################################################
#
#
#       RFC::RFCPID
#
#
# This class defines a virtual PID control device as well as a more 
# simplified control loop. for controling complex systems. 
#
# To obtain an indtance of this class, use one of the constructors:
#
# $id = RFC::RFCPID->new($name,$rig)
#
# $id = RFC::LApproach->new($name,$rig)
#
#  All RFCPID and derived objects have the following member functions (in addition to those
#  derived from RFC::BaseDevce and Debug.pm):
#
# $id->fast()            Returns yes if the device is to run continiously and
#                        as fast as possible. If no is returned it is only run
#                        once a minute (by crontab).
# $id->store()           Stores the current values and integrated error for
#                        Returns 1 (true) for succes, 0 in case of errors.
# $id->remove()          This function removes the underlying PID device from the
#                        current device. Is only to be used when it is certian that
#                        the current device is not to be used and no changes in the
#                        accumulated error for the device is wanted before device goes
#                        out of scope (nescesarry for the PID_fast_control.pl script
#                        for excluding slow PID's)
# $id->reset_int()       Resets the integrated error.
# $id->set_int($)        Sets the integrated error to the specified value.
# $id->find_int($)       Calculates and sets the required integrated error nescesarry to get
#                        the specified output value.
# $id->set([$])          Sets or gets the target setpoint.
# $id->data([$])         Reads the current value and computes the error which is
#                        stored internally as well as returned.
#                        If an argument is specified, this is assumed to be the
#                        current value and is used instead.
# $id->out()             Sets the output device to the calculated setpoint based
#                        on the PID settings.
# $id->control()         Reads the current value and sets the output (combination of
#                        data and out.
# $id->output_enabled()  Returns 1 if commands will be passed to the output device 
#                        0 otherwise.
# $id->show_gain()       Returns an array with information about the P, I, D and 
#                        combined output for the device
#
# Note however, that some of the functions are only dummy functions for the linear 
# approach device (reset_int, set_int, find_int, show_gain).
#
#
# The difference between LinearApproach (RFC::LApproach) and a true PID is that
# the linear approach device has a simpler configuration allowing for easier
# setup as tuning a true PID is notoriously tricky.
# The linear approach device also offers more stable operation in some cases.
# The disadvantages of the linear approach device is that it has a fixed regulation
# rate, and if the process value changes faster than the stepsize can account for, no
# stable tracking will be observed.
# An other disadvantage is that the linear approach device is prone to 'hunting' if
# the stepsize is set too high (this is similar to the PID device's tendency to
# oscilate if the P or I gains are set too high).
#
#################################################################
use strict;
use warnings;
use RFC::Header;
use RFC::BaseDevice;
use RFC::PID;
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->{'_type'} = 'RFCPID';
    $self->{'_class'} = 'PID';
    $self->{'_mode'} = 'Automatic';
    $self->{'_monitor_rows'} = '3,5,7,9,11';
    $self->{'_values_mode'} = ['Automatic'];
    $self->{'_values_class'} = [RFC::Device::list_PID()];
    $self->{'_setup_tags'} = ['P','I','D','intwindup','deadband','cutoff','min','max','sensor_type','sensor_device','control_type','control_device','allow_override','error_gain','fast','output_enabled','output_enable_input','show_plot'];
    $self->{'_reload_tags'} = ['type','sensor_type','sensor_device','control_type','control_device'];
    $self->{'_desc'} = {
	'class' => 'Device class',
	'P' => 'Proportional gain',
	'I' => 'Integrator gain',
	'D' => 'Differential gain',
	'intwindup' => 'Maximum integrated error',
	'deadband' => 'Deadband, whenever the absolute error is less than this no change in ouptut is made (determined before error gain is applied!). Note that the deadband should not be set to a value less than the accuracy of the sensor measuring the actual value (and hence the error)!',
	'cutoff' => 'Maximum error that will result in an iteration. If the error is greater than this value, no iteration will occour (similar to the deadband, except for large errors). If no value is specified, this feature is disabled (Default)',
	'min' => 'minimum allowed output (check that it is greater than output device minimum output!)',
	'max' => 'maximum allowed output (check that it is less than output device maximum output!)',
	'sensor_type' => 'Device type of the sensing device',
	'sensor_device' => 'Device name of sensing device',
	'control_type' => 'Device type of the controlled device',
	'allow_override' => 'Allow controlled device to be controlled directly from the GUI',
	'control_device' => 'Device name of controlled device. Dhe device instance must support the set() member function!',
	'error_gain' => 'Factor multiplied on the error. Used to keep the error values within the most optimum range. For RFC devices with long time constants usually a value of 0.01 should be chosen. Note that error_gain can be used to invert the output by changing the sign.',
	'output_enabled' => 'Determines if each call to out() or control() should result in commands passed to the output device (closed loop) or merely result in an iteration and resulting update of the integrated error (open loop). Setting this to No is usefull for calibration / configuration / testing purposes',
	'output_enable_input' => 'relay device determining if output is enabled or not',
	'fast' => 'Determines if the PID control loop is running as fast as possible (Yes) or only with one iteratoion / minute (No)',
    };
    $self->{'_default'} = {
	'type' => 'PID',
	'P' => 0.8,
	'I' => 0.3,
	'D' => 0.1,
	'intwindup' => 10,
	'deadband' => 0,
	'setpoint' => 0,
	'cutoff' => 0,
	'min' => 0,
	'max' => 1,
	'sensor_type' => 'simplechannel',
	'control_type' => 'gas',
	'error_gain' => 0.1,
	'allow_override' => 'No',
	'fast' => 'No',
	'output_enabled' => 'Yes',
    };
    bless $self,$class;
    $self->load_conf();
    $self->{'_set_fn_names'} = ['set']; # List of function names which can trigger a notify event
    $self->{'_ui_fn_names'} = ['reset_int','set_int','show_gain','find_int']; # List of function names which can be called from the device setup UI
    $self->{'_ui_fn_args'} = {'set_int' => 1,'find_int' => 1}; # Hash with number of args for function listed in _ui_fn_names 
    $self->{'_ui_fn_desc'} = {
	'set_int' => 'Sets the accumulated (integrated) error to a known (specified) value.',
	'find_int' => 'Sets the accumulated (integrated) error to a value which would result in the specified output.',
	'reset_int' => 'Resets the accumulated (integrated) error of the PID device.',
	'show_gain' => 'Shows internal P,I,D variables as well as output.',
    };
    $self->{'_values_sensor_type'} = [$self->rig->list_types()];
    $self->{'_values_fast'} = ['Yes','No'];
    $self->{'_values_allow_override'} = ['Yes','No'];
    $self->{'_values_output_enabled'} = ['Yes','No','Relay'];
    $self->{'_values_control_type'} = [$self->rig->list_types()];
    $self->{'_values_output_enable_input'} = ['',$self->rig->avaliable_devices('relay')];
    $self->{'_values_sensor_device'} = ['',$self->rig->avaliable_devices($self->{'_sensor_type'})];
    $self->{'_section'} = 'PID_'.$self->name;
    $self->{'_PID_lock'} = '/home/celltest/RFCRig_'.$self->rig->name.'.lock';
    $self->{'_values_control_device'} = [''];
    ## Sanitise names ###
    my $rname = $self->rig->name();
    $rname =~ /(\w+)/;
    $rname = $1;
    my $sname = $self->name();
    $sname =~ /(\w+)/;
    $sname = $1;
    $self->{'_file'} = $userdir.'rig'.$rname.'/PID_'.$sname;
    $self->{'_reset'} = $userdir.'rig'.$rname.'/PID_reset_'.$sname;
    $self->{'_PID'} = RFC::PID->new($self->name,$self->{'_file'},$self->{'_PID_lock'});
    return $self->init_common();
}


sub init()
{
    my $self = shift;
    return $self unless($self->{'_PID'});
    $self->{'_PID'}->load(); ## Load data on implicit init ###
    return $self->init_common();
}

sub init_common()
{
    my $self = shift;
    $self->{'_values_control_device'} = ['','TESTY1'];
    foreach my $tag ($self->setup_tags,'setpoint')
    {
        $self->{'_'.$tag} = $self->get_default($tag);
        my $conf = $self->get_conf($tag);
        $self->{'_'.$tag} = $conf if (defined($conf) && $conf =~ /\w/);
    }
    $self->die("Error: deadband must be zero or positive!") if ($self->{'_deadband'} < 0);
    if ($self->{'_sensor_type'})
    {
	$self->{'_values_sensor_device'} = ['',$self->rig->avaliable_devices($self->{'_sensor_type'})];
    }
    if ($self->{'_control_type'})
    {
	$self->{'_values_control_device'} = ['',$self->rig->avaliable_devices($self->{'_control_type'})];
    }
    $self->die("Error: Minimum output must be less than maximum output (Min: $self->{'_min'}, Max: $self->{'_max'})") unless ($self->{'_max'} > $self->{'_min'});
    if ($self->{'_cutoff'} && $self->{'_deadband'} && $self->{'_deadband'} > $self->{'_cutoff'})
    {
	$self->die("Cutoff must be larger than deadband! Either remove the cutoff completely (disable it) or set it to a value higher than the deadband!");
    }
    $self->{'_PID'}->P($self->{'_P'});
    $self->{'_PID'}->I($self->{'_I'});
    $self->{'_PID'}->D($self->{'_D'});
    $self->{'_PID'}->deadband($self->{'_deadband'} * abs($self->{'_error_gain'}));
    $self->{'_PID'}->cutoff($self->{'_cutoff'} * abs($self->{'_error_gain'}));
    $self->{'_PID'}->intwindup($self->{'_intwindup'});
    $self->{'_PID'}->max($self->{'_max'});
    $self->{'_PID'}->min($self->{'_min'});
    if ($self->{'_output_enabled'} && $self->{'_output_enabled'} =~ /relay/i && $self->{'_output_enable_input'})
    {
	my $dev = $self->rig->get_device('relay',$self->{'_output_enable_input'});
	unless ($dev)
	{
	    $dev = $self->rig->init_device('relay',$self->{'_output_enable_input'});
	    $dev->readstring_ignore(1) if ($dev);
	}
	$self->{'_enable_dev'} = $dev; 
    }
    foreach my $d ('_sensor','_control')
    {
	my $type = $self->{$d.'_type'};
	my $name = $self->{$d.'_device'};
	next unless ($self->{$d.'_device'});
	my $dev = $self->rig->get_device($type,$name);
	$dev->set_readonly() if ( $dev && !($self->{'_allow_override'} && $self-> {'_allow_override'} eq 'Yes'));
	unless ($dev)
	{
	    $dev = $self->rig->init_device($type,$name);
	    $dev->readstring_ignore(1) if ($dev);
	}
	$self->{'_dev'.$d} = $dev if ($dev);
    }
    my @list = $self->rig->avaliable_devices($self->{'_control_type'});
    my @ll = ();
    foreach my $i (@list)
    {
	push @ll,$i unless ($i eq $self->name());
    }
    $self->{'_values_control_device'} = ['',@ll];	
    return $self;
}


sub show_gain()
{
    my $self = shift;
    my @arr = ('PID regulator '.$self->name);
    push @arr,'Error: '.$self->data();
    push @arr,'Integrated error: '.$self->{'_PID'}->get_int() if ($self->{'_PID'});
    push @arr,'P: '.$self->{'_PID'}->get_P() if ($self->{'_PID'});
    push @arr,'I: '.$self->{'_PID'}->get_I() if ($self->{'_PID'});
    push @arr,'D: '.$self->{'_PID'}->get_D() if ($self->{'_PID'});
    push @arr,'Output: '.$self->{'_PID'}->Out() if ($self->{'_PID'});
    return @arr;
}

sub fast
{
    my $self = shift;
    return 1 if ($self->{'_fast'} && $self->{'_fast'} =~ /yes/i);
    return 0;
}

sub debug
{
    my $self = shift;
    if (@_)
    {
	$self->SUPER::debug(@_);
	$self->{'_PID'}->debug(@_) if ($self->{'_PID'});
	$self->{'_dev_control'}->debug(@_) if ($self->{'_dev_control'}); 
    }
    return $self->{'_debug'};
}

sub store()
{
    my $self = shift;
    if ($self->{'_PID'})
    {
	return $self->{'_PID'}->store();
    }
    else
    {
	$self->writedebug("WARNING: Attempting to store PID after it has been removed!",5) unless ($self->{'_PID'});
	return 0;
    }
}

sub find_int()
{
    my $self = shift;
    my $target = shift;
    return "Unspecified output target value, aborting!" unless (defined($target) && $target =~ /\d/);
    unless ($self->{'_PID'})
    {
	$self->writedebug("WARNING: Attempting to call set_int on PID after it has been removed!",5);
	return "No PID device!";
    }
    my $min = $self->{'_min'};
    my $max = $self->{'_max'};
    if ($target < $min || $target > $max)
    {
	return "Warning: specified output $target is outside legal range [$min - $max]!";
    }
    my $range = $max - $min;
    my $mid = $min + $range / 2;
    my $offset = $target - $mid;
    my $int = $offset * 2 / ($range * $self->{'_I'});
    if (abs($int) > $self->{'_intwindup'})
    {
	return "Warning: specified output requires a integerated edrror ($int) larger than the maximum allowed (+/- $self->{'_intwindup'})!";
    }
    $self->{'_PID'}->set_int($int);
    my $res = $self->{'_PID'}->store();
    unless ($res)
    {
	my $str = "Error: Could not permanently set integrated error to $int";
	$self->proglog($str.' for PID '.$self->name,@_);
	return $str;
    }
    $self->proglog("Setting integrated error for PID ".$self->name." to $int",@_);
    return "Found and applied solution: $int\n";
}


sub set_int()
{
    my $self = shift;
    my $value = shift;
    $value = 0 unless ($value && $value =~ /\d/);
    unless ($self->{'_PID'})
    {
	$self->writedebug("WARNING: Attempting to call set_int on PID after it has been removed!",5);
	return "No PID device!";
    }
    $self->{'_PID'}->set_int($value);
    my $res = $self->{'_PID'}->store();
    unless ($res)
    {
	my $str = "Error: Could not permanently set integrated error to $value";
	$self->proglog($str.' for PID '.$self->name,@_);
	return $str;
    }    
    $self->proglog("Setting integrated error for PID ".$self->name." to $value",@_);
    return 'Integrated error set to '.$value;
}

sub reset_int()
{
    my $self = shift;
    unless ($self->{'_PID'})
    {
	$self->writedebug("WARNING: Attempting to call set_int on PID after it has been removed!",5);
	return "No PID device!";
    }
    $self->{'_PID'}->reset_int();
    my $res = $self->{'_PID'}->store();
    unless ($res)
    {
	my $str = "Error: Could not permanently reset integrated error";
	$self->proglog($str.' for PID '.$self->name,@_);
	return $str;
    }
    $self->proglog("Resetting integrated error for PID ".$self->name,@_);
    return 'Integrated error reset';
}


sub set
{
    my $self = shift;
    if (@_)
    {
	my $class = $self->{'_class'};
	$self->{'_setpoint'} = shift;
	$self->rig->change_config_value('PID_'.$self->name,'setpoint',$self->{'_setpoint'});
	$self->writedebug("Setting setpoint for $class ".$self->name." to ".$self->{'_setpoint'});
	$self->proglog("Setting setpoint for $class ".$self->name." to ".$self->{'_setpoint'},@_);
	$self->notify('set',$self->{'_setpoint'},@_); 
    }
    return $self->{'_setpoint'};
}

sub data
{
    my $self = shift;
    my $value = 0;
    if (@_)
    {
	if (ref($_[0]))
	{
	    $self->die("No input device found for PID device ".$self->name.' when getting new value') unless ($self->{'_dev_sensor'});
	    $value = $self->{'_dev_sensor'}->read(@_);
	}
	else
	{
	    $value = $_[0];
	}
    }
    else
    {
	$self->die("No input device found for PID device ".$self->name.' when getting new value') unless ($self->{'_dev_sensor'});
	$value = $self->{'_dev_sensor'}->read;
    }
    $self->writedebug('Value for input device '.$self->{'_dev_sensor'}->name.' was: '.$value) if ($self->debug);
    unless (defined($value))
    {
	$self->writedebug('No data was returned by input device, no calculations performed!') if ($self->debug);
	return undef;
    }
    my $error = ($self->{'_setpoint' } - $value) * $self->{'_error_gain'};
    $self->writedebug('Calculated error based on setpoint '.$self->{'_setpoint'}.' and gain '.$self->{'_error_gain'}.' was: '.$error) if ($self->debug);
    $self->writedebug("WARNING: Attempting to set data in PID after it has been removed!",5)  unless ($self->{'_PID'});
    $self->{'_PID'}->data($error) if ($self->{'_PID'});
    return $error;
}

sub output_enabled
{
    my $self = shift;
    return 0 if ($self->{'_output_enabled'} && $self->{'_output_enabled'} =~ /No/i);
    return $self->{'_enable_dev'}->read(@_) if ($self->{'_enable_dev'});
    return 1;
}

sub out
{
    my $self = shift;
    if ($self->{'_PID'})
    {
	my $val = $self->{'_PID'}->Out(); 
	if (defined($val) && $self->output_enabled)
	{
	    ### Only set output if defined value        ###
	    ### Output value vill be undefined if error ###
	    ### is within the deadband                  ###
	    my $out = sprintf("%.4f",$val);
	    $self->{'_dev_control'}->set($out,$0,'nolog');
	    $self->writedebug("Setting output to : $out") if ($self->debug);
	    return $out;
	}
	return undef;
    }
    else
    {
	$self->writedebug("WARNING: Attempting to get PID output after it has been removed!",5)  unless ($self->{'_PID'});
    }
}

sub control
{
    my $self = shift;
    $self->data(@_);
    return $self->out();
}

sub read
{
    my $self = shift;
    return $self->{'_setpoint'};
}

sub readstring
{
    my $self = shift;
    return undef if ($self->readstring_ignore);    
    my $val = $self->read();
    my $error = $self->data(@_);
    my @t = $self->get_time();
    my $t = join(':',@t);
    my $res = join(' ',$self->name.'_setpoint',$t,time,$val,$self->name.'_error',$t,time,$error);
    $self->writedebug($res) if ($self->debug > 1);
    return $res;

}

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


sub test_string
{
    my $self = shift;
    my $string = $self->{'_class'}.' '.$self->name."\nSetpoint: ".$self->{'_setpoint'}."\n";
    if ($self->{'_dev_sensor'})
    {
	$string .= $self->{'_dev_sensor'}->test_string(@_)."\n";
    }
    else
    {
	$string .= "No input device!\n";
    }
    if ($self->{'_dev_control'})
    {
	$string .= $self->{'_dev_control'}->test_string(@_)."\n";
    }
    else
    {
	$string .= "No output device!\n";
    }
    if ($self->{'_enable_dev'})
    {
	$string .= $self->{'_enable_dev'}->test_string(@_)."\n";
    } 
    $string .= "Output disabled\n" unless ($self->output_enabled && $self->{'_dev_control'});
    return $string;
}

sub test
{
    my $self = shift;
    return $self->{'_dev_sensor'} && $self->{'_dev_control'} && $self->{'_dev_sensor'}->test() && $self->{'_dev_control'}->test();
}

sub monitor()
{
    my $self = shift;
    my $int = 'Na';
    my $out = 'Na';
    my $value = $self->{'_dev_sensor'}->read;
    my $error = $self->data($value);
    if ($self->{'_PID'})
    {
	$int = $self->{'_PID'}->get_int();
	$out = $self->{'_PID'}->Out();
    }
    foreach ($value,$error,$int,$out)
    {
	$_ = '' unless (defined($_));
    }
    my $str = 'Value: '.$value.' Setpoint: '.$self->{'_setpoint'}.' Error: '.$error.' Integrated_error: '.$int.' Output: '.$out;
    $str .= ' Output disabled' unless ($self->output_enabled);
    $self->writedebug($str) if ($self->debug);
    return $str;
}

sub print_config
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Type: PID';
    foreach my $tag ($self->setup_tags,'setpoint')
    {
	next unless (defined($self->{'_'.$tag}));
	push @arr,$tag.': '.$self->{'_'.$tag};
    }    
    push @arr, "Output disabled" unless ($self->output_enabled && $self->{'_dev_control'});
    return join (', ',@arr);
}

sub remove()
{
    my $self = shift;
    $self->writedebug("Removing PID from RFCPID device ".$self->name) if ($self->debug);
    $self->{'_PID'}->remove() if ($self->{'_PID'});
    $self->{'_PID'} = undef;
}

sub DESTROY()
{
    my $self = shift;
    $self->{'_PID'}->DESTROY() if ($self->{'_PID'});
    $self->{'_PID'} = undef;
}



package RFC::LApproach;
our @ISA = qw(RFC::RFCPID);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'_type'} = 'LinearApproach';
    $self->{'_class'} = 'LinearApproach';
    $self->{'_setup_tags'} = ['stepsize','deadband','min','max','sensor_type','sensor_device','control_type','control_device','allow_override','inverted_output','fast','output_enabled','output_enable_input','show_plot'];
    $self->{'_desc'}{'inverted_output'} = 'Determines if the setpoint is addjusted towards positive (Yes) instead of towards negative (No) if the process value is above the setpoint';
    $self->{'_desc'}{'stepsize'} = 'The amount the setpoint is addjusted up or down at each iteration if process variable is outside the deadband (minimum is 0.0001)';
    $self->{'_default'} = {
	'type' => 'LinearApproach',
	'inverted_output' => 'No',
	'deadband' => 0,
	'stepsize' => 0.01,
	'setpoint' => 0,
	'min' => 0,
	'max' => 1,
	'sensor_type' => 'simplechannel',
	'control_type' => 'gas',
	'allow_override' => 'No',
	'fast' => 'No',
	'output_enabled' => 'Yes',
    };
    $self->{'_set_fn_names'} = ['set']; # List of function names which can trigger a notify event
    $self->{'_ui_fn_names'} = []; # List of function names which can be called from the device setup UI
    $self->{'_ui_fn_args'} = {}; # Hash with number of args for function listed in _ui_fn_names 
    $self->{'_ui_fn_desc'} = {};
    $self->{'_values_inverted_output'} = ['Yes','No'];
    bless $self,$class;
    $self->{'_removed'} = 0;
    return $self->init_common();
}

sub init()
{
    my $self = shift;
    return $self->init_common();
}


sub init_common()
{
    my $self = shift;
    foreach my $tag ($self->setup_tags,'setpoint')
    {
        $self->{'_'.$tag} = $self->get_default($tag);
        my $conf = $self->get_conf($tag);
        $self->{'_'.$tag} = $conf if (defined($conf) && $conf =~ /\w/);
    }
    $self->{'_PID'} = undef;
    ## Minimum stepsize: 0.0001 ##
    $self->{'_stepsize'} = 0.0001 unless ($self->{'_stepsize'} && $self->{'_stepsize'} > 0.0001); 
    if ($self->{'_output_enabled'} && $self->{'_output_enabled'} =~ /relay/i && $self->{'_output_enable_input'})
    {
	my $dev = $self->rig->get_device('relay',$self->{'_output_enable_input'});
	unless ($dev)
	{
	    $dev = $self->rig->init_device('relay',$self->{'_output_enable_input'});
	    $dev->readstring_ignore(1) if ($dev);
	}
	$self->{'_enable_dev'} = $dev; 
    }
    if ($self->{'_sensor_type'})
    {
        $self->{'_values_sensor_device'} = ['',$self->rig->avaliable_devices($self->{'_sensor_type'})];
    }
    if ($self->{'_control_type'})
    {
        $self->{'_values_control_device'} = ['',$self->rig->avaliable_devices($self->{'_control_type'})];
    }
    foreach my $d ('_sensor','_control')
    {
	my $type = $self->{$d.'_type'};
	my $name = $self->{$d.'_device'};
	next unless ($self->{$d.'_device'});
	my $dev = $self->rig->get_device($type,$name);
	$dev->set_readonly() if ( $dev && !($self->{'_allow_override'} && $self-> {'_allow_override'} eq 'Yes'));
	unless ($dev)
	{
	    $dev = $self->rig->init_device($type,$name);
	    $dev->readstring_ignore(1) if ($dev);
	}
	$self->{'_dev'.$d} = $dev if ($dev);
    }
    my @list = $self->rig->avaliable_devices($self->{'_control_type'});
    my @ll = ();
    foreach my $i (@list)
    {
	push @ll,$i unless ($i eq $self->name());
    }
    $self->{'_values_control_device'} = ['',@ll];	
    return $self;
}


sub print_config
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Type: Linear approach';
    foreach my $tag ($self->setup_tags,'setpoint')
    {
	next unless (defined($self->{'_'.$tag}));
	push @arr,$tag.': '.$self->{'_'.$tag};
    }    
    push @arr, "Output disabled" unless ($self->output_enabled && $self->{'_dev_control'});
    return join (', ',@arr);
}

sub monitor()
{
    my $self = shift;
    my $value = $self->{'_dev_sensor'}->read(@_);
    my $error = $self->data($value);
    my $out = $self->{'_dev_control'}->setpoint();
    foreach ($value,$error,$out)
    {
	$_ = '' unless (defined($_));
    }
    my $str = 'Value: '.$value.' Setpoint: '.$self->{'_setpoint'}.' Error: '.$error.' Output: '.$out;
    $str .= ' Output disabled' unless ($self->output_enabled);
    $self->writedebug($str) if ($self->debug);
    return $str;
}

sub data
{
    my $self = shift;
    my $class = $self->{'_class'};
    my $value = 0;
    if (@_)
    {
	if (ref($_[0]))
	{
	    $self->die("No input device found for $class device ".$self->name.' when getting new value') unless ($self->{'_dev_sensor'});
	    $value = $self->{'_dev_sensor'}->read(@_);
	}
	else
	{
	    $value = $_[0];
	}
    }
    else
    {
	$self->die("No input device found for $class device ".$self->name.' when getting new value') unless ($self->{'_dev_sensor'});
	$value = $self->{'_dev_sensor'}->read;
    }
    $self->writedebug('Value for input device '.$self->{'_dev_sensor'}->name.' was: '.$value) if ($self->debug);
    unless (defined($value))
    {
	$self->writedebug('No data was returned by input device, no calculations performed!') if ($self->debug);
	return undef;
    }
    my $error = $self->{'_setpoint' } - $value;
    $self->writedebug('Calculated error based on setpoint '.$self->{'_setpoint'}.' was: '.$error) if ($self->debug);
    return $error;
}

sub control
{
    my $self = shift;
    return $self->out();
}

sub out
{
    my $self = shift;
    return undef if ($self->{'_removed'});
    my $error = $self->data(@_);
    $error = undef if (abs($error) <= $self->{'_deadband'});
    if (defined($error) && $self->output_enabled)
    {
	### Only set output if defined value        ###
	### Output value vill be undefined if error ###
	### is within the deadband                  ###
	my $val = $self->{'_dev_control'}->setpoint();
	my $addj = 0;
	if ($error < 0)
	{
	    ## Increase setpoint if error is negative
	    $addj = $self->{'_stepsize'};
	}
	else
	{
	    ## Decrease setpoint if error is positive
	    $addj = -$self->{'_stepsize'};
	}
	### Invert addjustment if invert_output is on
	$addj = -$addj if ($self->{'_inverted_output'} && $self->{'_inverted_output'} =~ /yes/i);
	my $out = sprintf("%.4f",($val + $addj));
	$out = $self->{'_min'} if ($out < $self->{'_min'});
	$out = $self->{'_max'} if ($out > $self->{'_max'});
	$self->{'_dev_control'}->set($out,$0,'nolog');
	$self->writedebug("Setting output to : $out") if ($self->debug);
	return $out;
    }
    return undef;
}

sub store()
{
    my $self = shift;
    return 1;
}

sub remove()
{
    my $self = shift;
    $self->{'_removed'} = 1;
    return undef;
}

sub DESTROY()
{
    my $self = shift;
    return undef;
}

sub show_gain()
{
    my $self = shift;
    return 'Warning: this function is not avaliable for '.$self->{'_class'};
}


sub find_int()
{
    my $self = shift;
    return 'Warning: this function is not avaliable for '.$self->{'_class'};
}


sub set_int()
{
    my $self = shift;
    return 'Warning: this function is not avaliable for '.$self->{'_class'};
}

sub reset_int()
{
    my $self = shift;
    return 'Warning: this function is not avaliable for '.$self->{'_class'};
}


return 1;
