package RFC::Filter;
#$Id: Filter.pm,v 1.88 2023/04/28 09:15:13 sqko Exp $
#################################################################
#
#
#       RFC::Filter
#
# This class defines a special virtual device which can be used
# as a sort of filter on top of a normal device. An input Filter device
# type operates on an other device and converts all read 
# operations on the underlying device
#
# An output filter device operates on all set() function calls and
# filters the supplied argument before passing it on to the underlying
# device.
#
# An input-output filter device does conversion on both read and set
# operations.
#
# All control commands are passed on to the underlying device and 
# the resulting filter device can be operated on as if it were the 
# underlying device.
#
# The Filter device class defines no new member functions and classes
# derived from Filter should not normally define any new functions either. 
#
# The filter class is not intended to be instantiated directly,
# only the derived classes.
# 
# Notice that the class instance created by the constructors of 
# deviced derived by Filter must NOT call init() as part of the 
# constructor. 
# This must first be done as part of any of the handling functions.
# This is to allow the underlying device to be autovivified
# before it is used.
#
# A special type of Filter is the Schmidt Trigger device.
# This device class contains a nomral device (simple input device
# usually) which is masqueraded as a read only relay device (that is,  
# read returns either 0 or 1). The exact output value depends on the 
# value of the contained device as well as the settings of the trigger
# device.
# 
# To obtain an instance of the schmid trigger device call the constructer:
#
# my $id = RFC::Strigger->new($name,$rig);
#
#
# A number of other filter classes are also directly avaliable from RFC:Filter.
# These are the Lowpass, Integer, MovingAverage, Integrator and Derivative
# as well as the Truncate classes.
# They can be obtained by calling the constructors:
#
# my $id = RFC::Integer->new($name,$rig);
#
# my $id = RFC::Lowpass->new($name,$rig);
#
# my $id = RFC::MovingAverage->new($name,$rig);
#
# my $id = RFC::CircularAverage->new($name,$rig);
#
# my $id = RFC::Integrator->new($name,$rig);
#
# my $id = RFC::Derivative->new($name,$rig);
#
# The Derivative filter class contains an extra public member 
# funciton which is the get_confidence()
#
# This function will return difference between the returned derivative
# value and the positive 95% confidence interval limit.
# Thus the full interval is: read() - get_confidence() to read() + 
# get_confidence().
# Note that each call to read_nocache will make a nex measurement and
# thus calculate a new confidence value!
#
# The Integrator class also defines a special member function,
# which is the set_int() function which allows programs to specifically
# set the integrated value to a specific value. If no argument is 
# supplied to set_int() the value is assuemd to be 0.
#
# The CircularAverage filter works on circular data (such as directions)
# and will do a moving average using vector calculations on the
# data represented as complex numbers:
# avg = sum(cpl(xi(1->n)))/n
#
# The Truncate device is a special device which returns the measured
# value some time in the past truncated to last whole hour or day etc.
# Default is to truncate to last whole hour.
# The truncate device can also be used to retain values in the past
# by specifying an offset. Note however that only integer values
# can be used for the offset and that the offset is in units
# of the interval.
# Thus if the interval is in 2 hours and the offset is 3, the
# device will return a value logged between 6 and 8 hours hours ago
# (as data will be  truncated to the specified interval)
#################################################################

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


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 $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Filter_device';
    $self->{'_html_template'} = 'Filter.tt';
    $self->{'_setup_tags'} = ['class','mode','device_type','device','title','show_plot','read_function'];
    $self->{'_reload_tags'} = ['class','mode','device_type','device','output_type','output_device','inputs'];
    $self->{'_setup_tags_readonly'} = ['mode'];
    $self->{'_desc'} = {
        'mode' => 'Device control mode',
	'device_type' => 'Type of raw device',
        'device' => 'Raw device',
        'title' => 'Optional title',
        'read_function' => 'Device function to use for read operations',
    };
    $self->{'_default'} = {
        'device_type' => 'simplechannel',
        'device' => '',
        'read_function' => 'read',
        'show_plot'=>'Yes',
    };
    bless $self,$class;
    $self->{'_values_class'} = [RFC::Device::list_filter()];
    $self->{'_values_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_section'} = 'filter_'.$self->name;
    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/);
    }
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
	push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    $self->{'_values_device'} = ['',@list];
    $self->{'_values_read_function'} = ['read'];
    if ($self->{'_device_type'} && $self->{'_device'})
    {
	### get device if it already exist ###
	my $dev = $self->rig->get_device($self->{'_device_type'},$self->{'_device'});
	$self->{'_dev'} = $dev if ($dev);	
	if ($dev)
	{
	    my @arr = $dev->list_set_functions();
	    $self->{'_set_fn_names'} = \@arr;
	    my @arr2 = ('read');
	    @arr2 = $self->{'_dev'}->read_names();
	    $self->{'_values_read_function'} = \@arr2;
	}
    }
    $self->{'_info_string'} = 'Note that filter devices used in a control chain only works correctly if they are included (enabled)';
    ### Note, do NOT run init as part of constructor! ###
    return $self;
}

sub test
{
    my $self = shift;
    $self->init();
    return $self->{'_dev'}->test();
}

sub Accept
{
    my $self = shift;
    my $visitor = shift;
    $self->init();
    $self->{'_dev'}->Accept($visitor);
    $visitor->VisitComplex($self);
    return undef;
}

sub attatch_observer($)
{
    my $self = shift;
    $self->init();
    return $self->{'_dev'}->attatch_observer(@_);
}


sub detatch_observer($)
{
    my $self = shift;
    $self->init();
    return $self->{'_dev'}->detatch_observer(@_);
}


sub debug
{
    my $self = shift;
    $self->SUPER::debug(@_);
    if (@_)
    {
        #### Pass on debug level to all objects honoring this ###
	$self->{'_dev'}->debug($self->{'_debug'}) if ($self->{'_dev'});
    }
    return $self->{'_debug'};    
}


sub notify
{
    my $self = shift;
    $self->init();
    return $self->{'_dev'}->notify(@_);
}

sub observe
{
    my $self = shift;
    $self->init();
    return $self->{'_dev'}->observe(@_);
}

sub init()
{
    my $self = shift;
    if (!$self->{'_dev'} && $self->{'_device_type'} && $self->{'_device'})
    {
	### get device if it already exist                                          ###
	### It may have been created after SPDEV constructor ran and call of init() ###
	my $dev = $self->rig->get_device($self->{'_device_type'},$self->{'_device'});
	### Initialise it if it doesn't ###
	unless ($dev)
	{
	    $dev = $self->rig->init_device($self->{'_device_type'},$self->{'_device'});
	    $dev->readstring_ignore(1) if ($dev);
	}
	$self->{'_dev'} = $dev if ($dev);
	$dev->debug($self->debug);
	$self->die("Could not initialise filter device of type '".$self->{'_device_type'}."' with name: '".$self->{'_device'}."'") unless ($self->{'_dev'});
    }
    if ($self->{'_dev'})
    {
	my @arr = $self->{'_dev'}->read_names();
	$self->{'_values_read_function'} = \@arr;
	my @arr2 = ();
	@arr2 = $self->{'_dev'}->list_set_functions();
	$self->{'_set_fn_names'} = \@arr2;
	$self->{'_readstring_furnace'} = $self->{'_dev'}{'_readstring_furnace'};
    }
    return $self->local_init();
}

sub local_init()
{
    #### Stub function intended for loverloading for any impelmention dependent initialisations ###
    my $self = shift;
    return $self;
}

sub readonly()
{
    my $self = shift;
    $self->init();
    return $self->device->readonly() if ($self->device);
    return undef;
}

sub device()
{
    my $self = shift;
    $self->init();
    return $self->{'_dev'};
}

sub readonly_value($)
{
    my $self = shift;
    my $tag = shift;
    return undef unless ($tag);
    ### allow call to get 'mode' for undefined devices ###
    return 'Na' if ($tag eq 'mode' && !$self->{'_device'});
    $self->init();
    $self->writedebug("Executing readonly function $tag") if ($self->debug());
    if ($self->device)
    {
	return $self->device->mode() if ($tag eq 'mode');
	return $self->device->readonly_value($tag);
    }
}

sub read_named
{
    my $self = shift;
    $self->init();
    if ($_[0] eq 'read')
    {
	shift;
	$self->writedebug("Executing read trough read_named") if ($self->debug());
	return $self->read(@_); 
    }
    $self->writedebug("Executing function ".$_[0]." with arguments: ".join(', ',@_[1,-1])) if ($self->debug());
    return $self->{'_dev'}->read_named(@_) if ($self->{'_dev'});
}

sub AUTOLOAD()
{
    ################################################################
    ### AUTOLOAD() , redirect all method calls not defined by    ###
    ### Basedevice to the underlying device                      ###
    ################################################################
    my $self = shift;
    $self->init();
    {
	no strict "refs";
	$AUTOLOAD =~ /\:\:(\w+)$/;
	return $self->{'_dev'}->$1(@_);
    }
}


package RFC::Strigger;
use RFC::Header;
use SemaforeFile;
our @ISA = qw(RFC::Filter);


sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Schmidt trigger latch';
    $self->{'_setup_tags'} = ['class','mode','device_type','device','shift_up','shift_down','reverse_output','title','show_plot'];
    $self->{'_setup_tags_readonly'} = ['mode'];
    $self->{'_desc'} = {
        'class' => 'Device class',
        'mode' => 'Device control mode',
        'device_type' => 'Type of raw device',
        'device' => 'Raw device',
	'shift_up' => 'Threshold above wich the trigger is on',
	'shift_down' => 'Threshold below which the trigger is off',
	'reverse_output' => 'Indicates if the locgical output state should be reversed',
        'title' => 'Optional title',
    };
    $self->{'_default'} = {
        'class' => 'Schmidttrigger',
        'device_type' => 'simplechannel',
        'device' => '',
	'mode' => 'Readonly',
	'shift_up' => 0.6,
	'shift_down' => 0.4,
        'show_plot'=>'Yes',
	'reverse_output' => 'No',
    };
    bless $self,$class;
    $self->{'_readonly'} = 1;
    $self->{'_mode'} = 'Readonly';
    $self->{'_info_string'} = 'Remember that shift_up must have a higher value than shift_down as otherwise undefined behaviour can be observed!';
    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/);
    }
    $self->{'_values_reverse_output'} = ['Yes','No'];
    $self->{'_file'} = SemaforeFile->new($userdir.'rig'.$self->rig->name.'/Filter_'.$self->name,'/home/celltest/Gas_rig'.$self->rig->name.'.lock');
    unless ($self->{'_file'}->exist)
    {
        ### Create file with rw for all           ###
        ### Yes I know 666 is the number of evil! ###
        $self->{'_file'}->lock_ex;
        $self->{'_file'}->writeline('0.00');
        chmod 0666, $self->{'_file'}->filename;
        $self->{'_file'}->unlock;
    }
    $self->rig->register_device('relay',$self);
    return $self;
}

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

sub read
{
    my $self = shift;
    my $value = $self->device->read(@_);
    my @lines = $self->{'_file'}->readlines;
    my $oldval = 0;
    $oldval = $lines[0] if (@lines);
    $oldval += 0; ## Nescesarry to get rid of trailing zeroes ###
    my $res = $oldval;
    if ($oldval && $value < $self->{'_shift_down'})
    {
	$self->{'_file'}->writeline(0);
	$res = 0;
    }
    if (!$oldval && $value > $self->{'_shift_up'})
    {
	$self->{'_file'}->writeline(1);
	$res = 1;
    }
    if ($self->{'_reverse_output'} =~ /yes/i)
    {
	if ($res)
	{
	    $res = 0;
	}
	else
	{
	    $res = 1;
	}
    }
    return $res;
}

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

sub test_string
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    my @arr = ();
    $self->init();
    my $string = 'Schmidt trigger device '.$self->name.': ';
    my $raw = $self->device->read_named($self->{'_read_function'},$data);
    my $val = $self->read($data);
    push @arr,'Raw read: '.$raw;
    push @arr,'value: '.$val;
    $string .= join(', ',@arr)."\n";
    return $string;
}

sub print_config()
{
    my $self = shift;
    $self->init();
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Type: '.$self->type;
    push @arr,'Shift up: '.$self->{'_shift_up'};
    push @arr,'Shift down: '.$self->{'_shift_down'};
    push @arr,'Reverse output: '.$self->{'_reverse_output'};
    push @arr,$self->{'_dev'}->print_config();
    return join(', ',@arr);
}

sub readonly_value($)
{
    my $self = shift;
    my $tag = shift;
    return undef unless ($tag);
    ### allow call to get 'mode' for undefined devices ###
    $self->init();
    return 'Readonly' if ($tag eq 'mode');
    return $self->device->readonly_value($tag) if ($self->device);
    return undef;
}

package RFC::Lowpass;
use RFC::Header;
use RFC::BaseDevice;
use Time::HiRes qw(time);
use SemaforeFile;
our @ISA = qw(RFC::BaseDevice);


sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Low pass filter';
    $self->{'_setup_tags'} = ['class','mode','device_type','device','time_constant','title','show_plot'];
    $self->{'_reload_tags'} = ['class','mode','device_type','device'];
    $self->{'_setup_tags_readonly'} = ['mode'];
    $self->{'_desc'} = {
        'class' => 'Device class',
        'mode' => 'Device control mode',
        'device_type' => 'Type of raw device',
        'device' => 'Raw device',
	'time_constant' => 'Time constant in seconds',
        'title' => 'Optional title',
        'read_function' => 'Device function to use for read operations',
    };
    $self->{'_default'} = {
        'class' => 'Lowpass',
        'device_type' => 'simplechannel',
        'device' => '',
	'mode' => 'Readonly',
	'time_constant' => 10,
	'read_function' => 'read',
        'show_plot'=>'Yes',
    };
    bless $self,$class;
    $self->{'_values_class'} = [RFC::Device::list_filter()];
    $self->{'_values_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_readonly'} = 1;
    $self->{'_mode'} = 'Readonly';
    $self->{'_section'} = 'filter_'.$self->name;
    $self->load_conf();
    $self->{'_file'} = SemaforeFile->new($userdir.'rig'.$self->rig->name.'/Filter_'.$self->name,'/home/celltest/Gas_rig'.$self->rig->name.'.lock');
    unless ($self->{'_file'}->exist)
    {
        ### Create file with rw for all           ###
        ### Yes I know 666 is the number of evil! ###
        $self->{'_file'}->lock_ex;
        $self->{'_file'}->writeline('0.00');
        chmod 0666, $self->{'_file'}->filename;
        $self->{'_file'}->unlock;
    }
    $self->rig->register_device($self->{'_device_type'},$self);
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
	push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    $self->{'_values_device'} = ['',@list];
    $self->{'_values_read_function'} = ['read'];
    if ($self->{'_device_type'} && $self->{'_device'})
    {
	### get device if it already exist ###
	my $dev = $self->rig->get_device($self->{'_device_type'},$self->{'_device'});
	if ($dev)
	{
	    $self->{'_dev'} = $dev;
	    my @arr = $dev->read_names();
	    $self->{'_values_read_function'} = \@arr;
	}
    }
    $self->{'_info_string'} = 'Note that filter devices used in a control chain only works correctly if they are included (enabled)';
    return $self;
}

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


sub init()
{
    my $self = shift;
    if (!$self->{'_dev'} && $self->{'_device_type'} && $self->{'_device'})
    {
	### get device if it already exist                                          ###
	### It may have been created after SPDEV constructor ran and call of init() ###
	my $dev = $self->rig->get_device($self->{'_device_type'},$self->{'_device'});
	### Initialise it if it doesn't ###
	unless ($dev)
	{
	    $dev = $self->rig->init_device($self->{'_device_type'},$self->{'_device'});
	    $dev->readstring_ignore(1) if ($dev);
	}
	$self->{'_dev'} = $dev if ($dev);
	$dev->debug($self->debug);
	$self->die("Could not initialise underlying device for filter device of type '".$self->type."' with name: '".$self->name."'") unless ($self->{'_dev'});
	my @arr = $self->{'_dev'}->read_names();
	$self->{'_values_read_function'} = \@arr;
    }
    return $self->local_init();
}

sub local_init()
{
    #### Stub function intended for loverloading for any impelmention dependent initialisations ###
    my $self = shift;
    return $self;
}


sub read
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    my $res = 0;
    $self->check_cache($data);
    my $cached = $data->get($self->type,$self->name);
    if (defined($cached))
    {
        $self->writedebug("Using already measured value for ".$self->name) if ($self->debug > 3); 
        $res = $cached;
    }
    else
    {
	my $value = $self->device->read_named($self->{'_read_function'},@_);
	$self->{'_file'}->lock_ex();
	my @lines = $self->{'_file'}->readlines;
	my $oldval = 0;
	my $oldtime = 0;
	$oldval = $lines[0] if (@lines);
	$oldtime = $lines[1] if (@lines);
	my $time = time;
	my $deltat = $oldtime - $time;
	my $deltax = $value - $oldval;
	my $diff = $deltax * (1 - exp($deltat / $self->{'_time_constant'}));
	$res = $oldval + $diff;
	$self->{'_file'}->writeline($res,$time);
	$self->{'_file'}->lock_un();
        $data->set($self->type,$self->name,$res);
        $self->writedebug("Measured value for ".$self->name.' was '.$res) if ($self->debug > 3); 
    }
    return $res;
}

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

sub device()
{
    my $self = shift;
    $self->init();
    return $self->{'_dev'};
}

sub test_string
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    my @arr = ();
    $self->init();
    my $string = 'Low pass filter '.$self->name.': ';
    my $raw = $self->device->read_named($self->{'_read_function'},$data);
    my $val = $self->read($data);
    push @arr,'Raw read: '.$raw;
    push @arr,'value: '.$val;
    $string .= join(', ',@arr)."\n";
    return $string;
}


sub test
{
    my $self = shift;
    $self->init();
    return $self->{'_dev'}->test();
}


sub print_config()
{
    my $self = shift;
    $self->init();
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Type: '.$self->type;
    push @arr,'Time constant: '.$self->{'_time_constant'};
    push @arr,$self->{'_dev'}->print_config();
    return join(', ',@arr);
}

sub readonly_value($)
{
    my $self = shift;
    my $tag = shift;
    return undef unless ($tag);
    ### allow call to get 'mode' for undefined devices ###
    $self->init();
    return 'Readonly' if ($tag eq 'mode');
    return $self->device->readonly_value($tag) if ($self->device);
    return undef;
}

sub attatch_observer($)
{
    my $self = shift;
    $self->init();
#    $self->SUPER->attatch_observer(@_);
    return $self->{'_dev'}->attatch_observer(\$self);
}


sub detatch_observer($)
{
    my $self = shift;
    $self->init();
#    $self->SUPER->attatch_observer(@_);
    return $self->{'_dev'}->detatch_observer(\$self);
}

sub observe($$)
{
    my $self = shift;
    my $action = shift;
    my $value = shift;
    if ($action eq 'setflow')
    {
	my $val = $self->read();
        $self->notify($action,$val,@_);
    }
    else
    {
        $self->notify($action,$value,@_);
    }
    return $self->SUPER::notify($action,$value,@_);
}

sub AUTOLOAD()
{
    ################################################################
    ### AUTOLOAD() , redirect all method calls not defined by    ###
    ### Basedevice to the underlying device                      ###
    ################################################################
    my $self = shift;
    $self->init();
    {
	no strict "refs";
	$AUTOLOAD =~ /\:\:(\w+)$/;
	return $self->{'_dev'}->$1(@_);
    }
}

package RFC::Integer;
use RFC::Header;
our @ISA = qw(RFC::Lowpass);


sub new
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Integer';
    $self->{'_setup_tags'} = ['class','mode','device_type','device','read_function'];
    $self->{'_reload_tags'} = ['class','mode','device_type','device'];
    $self->{'_setup_tags_readonly'} = ['mode','disable_readstring'];
    $self->{'_desc'} = {
        'class' => 'Device class',
        'mode' => 'Device control mode',
        'device_type' => 'Type of raw device',
        'device' => 'Raw device',
        'read_function' => 'Device function to use for read operations',
    };
    $self->{'_default'} = {
        'class' => 'Integer',
        'device_type' => 'simplechannel',
        'device' => '',
	'mode' => 'Readonly',
	'read_function' => 'read',
	'disable_readstring' => 'Yes',
    };
    bless $self,$class;
    $self->{'_values_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_readonly'} = 1;
    $self->{'_mode'} = 'Readonly';
    $self->load_conf();
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
	push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    $self->{'_values_device'} = ['',@list];
    $self->{'_values_read_function'} = ['read'];
    if ($self->{'_device_type'} && $self->{'_device'})
    {
	### get device if it already exist ###
	my $dev = $self->rig->get_device($self->{'_device_type'},$self->{'_device'});
	$self->{'_dev'} = $dev if ($dev);	
    }
    $self->{'_info_string'} = 'Note that filter devices used in a control chain only works correctly if they are included (enabled)';
    $self->readstring_ignore(1);
    return $self;
}


sub readonly_value($)
{
    my $self = shift;
    my $tag = shift;
    return undef unless ($tag);
    ### allow call to get 'mode' for undefined devices ###
    return 'Na' if ($tag eq 'mode' && !$self->{'_device'});
    return 'Yes' if ($tag eq 'disable_readstring');
    $self->init();
    $self->writedebug("Executing readonly function $tag") if ($self->debug());
    if ($self->device)
    {
	return $self->device->mode() if ($tag eq 'mode');
	return $self->device->readonly_value($tag);
    }
    return undef;
}

sub test_string
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    my @arr = ();
    $self->init();
    my $string = 'Integer filter '.$self->name.': ';
    my $raw = $self->device->read_named($self->{'_read_function'},$data);
    my $val = $self->read($data);
    push @arr,'Raw read: '.$raw;
    push @arr,'value: '.$val;
    $string .= join(', ',@arr)."\n";
    return $string;
}

sub read
{
    my $self = shift;
    my $data = undef;
    my $value = $self->device->read_named($self->{'_read_function'},@_);
    return sprintf("%.0f",$value);
}

sub print_config()
{
    my $self = shift;
    $self->init();
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Type: '.$self->type;
    push @arr,$self->{'_dev'}->print_config();
    return join(', ',@arr);
}





package RFC::Truncate;
use RFC::Header;
use DateTime;
use DateTime::Duration;
our @ISA = qw(RFC::Lowpass);

sub new
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Data truncate';
    $self->{'_setup_tags'} = ['class','mode','device_type','device','interval_type','interval','offset','read_function','title','show_plot'];
    $self->{'_reload_tags'} = ['class','mode','device_type','device'];
    $self->{'_setup_tags_readonly'} = ['mode'];
    $self->{'_desc'} = {
        'class' => 'Device class',
        'mode' => 'Device control mode',
        'device_type' => 'Type of raw device',
        'device' => 'Raw device',
	'interval' => 'Number of hours or days etc, Note only integers allowed!',
	'interval_type' => 'Timestamp to truncate data to',
        'title' => 'Optional title',
	'offset' => 'Time offset (in units of the interval)',
        'read_function' => 'Device function to use for read operations',
    };
    $self->{'_default'} = {
        'class' => 'Truncate',
        'device_type' => 'simplechannel',
        'device' => '',
	'mode' => 'Readonly',
	'interval' => 1,
	'interval_type' => 'Hour',
	'read_function' => 'read',
        'show_plot'=>'Yes',
	'offset' => 0,
    };
    bless $self,$class;
    
    $self->{'_values_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_values_interval_type'} = ['Minute','Hour','Day','Week','Month'];
    $self->{'_readonly'} = 1;
    $self->{'_mode'} = 'Readonly';
    $self->load_conf();
    $self->{'_file'} = SemaforeFile->new($userdir.'rig'.$self->rig->name.'/Filter_'.$self->name,'/home/celltest/Gas_rig'.$self->rig->name.'.lock');
    unless ($self->{'_file'}->exist)
    {
        ### Create file with rw for all           ###
        ### Yes I know 666 is the number of evil! ###
        $self->{'_file'}->lock_ex;
        $self->{'_file'}->writeline('0.00');
        chmod 0666, $self->{'_file'}->filename;
        $self->{'_file'}->unlock;
    }
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
	push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    $self->{'_values_device'} = ['',@list];
    $self->{'_values_read_function'} = ['read'];
    if ($self->{'_device_type'} && $self->{'_device'})
    {
	### get device if it already exist ###
	my $dev = $self->rig->get_device($self->{'_device_type'},$self->{'_device'});
	$self->{'_dev'} = $dev if ($dev);	
    }
    if ($self->{'_interval'} && $self->{'_interval'} =~ /(\d+)/)
    {
	$self->{'_interval'} = $1;
    }
    else
    {
	$self->{'_interval'} = 1;
    }
    if ($self->{'_offset'} && $self->{'_offset'} =~ /(\d+)/)
    {
	$self->{'_offset'} = $1;
    }
    else
    {
	$self->{'_offset'} = 0;
    }
    return $self;
}

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

sub test_string
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    my @arr = ();
    $self->init();
    my $string = 'Time truncate filter '.$self->name.': ';
    my $raw = $self->device->read_named($self->{'_read_function'},$data);
    my $val = $self->read($data);
    push @arr,'Raw read: '.$raw;
    push @arr,'value: '.$val;
    $string .= join(', ',@arr)."\n";
    return $string;
}

sub read
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    my $res = 0;
    $self->check_cache($data); 
    my $cached = $data->get($self->type,$self->name);
    if (defined($cached))
    {
        $self->writedebug("Using already measured value for ".$self->name) if ($self->debug > 3); 
        $res = $cached;
    }
    else
    {
	my $value = $self->device->read_named($self->{'_read_function'},@_);
	$self->{'_file'}->lock_ex;
	my @lines = $self->{'_file'}->readlines;
	my @newlines = ();
	if (@lines)
	{
	    my $dt = DateTime->now();
	    my $before;
	    my $befstr;
	    my $first;
	    my $n = $self->{'_interval'};
	    my $type = $self->{'_interval_type'};
	    my $offset = $self->{'_offset'} * $n;
	    if ($type eq 'Minute')
	    {
		$dt->truncate( 'to' => 'minute' );
		$before = $dt - DateTime::Duration->new( minutes => $n );
		$first = $before - DateTime::Duration->new( minutes => $offset );
	    }
	    elsif ($type eq 'Day')
	    {
		$dt->truncate( 'to' => 'day' );		
		$before = $dt - DateTime::Duration->new( days => $n );
		$first = $before - DateTime::Duration->new( days => $offset);
	    }
	    elsif ($type eq 'Week')
	    {
		$dt->truncate( 'to' => 'week' );				
		$before = $dt - DateTime::Duration->new( weeks => $n );
		$first = $before - DateTime::Duration->new( weeks => $offset );
	    }
	    elsif ($type eq 'Month')
	    {
		$dt->truncate( 'to' => 'month' );
		$before = $dt - DateTime::Duration->new( months => $n );
		$first = $before - DateTime::Duration->new( months => $offset );
	    }
	    else
	    {
		$dt->truncate( 'to' => 'hour' );
		$before = $dt - DateTime::Duration->new( hours => $n );
		$first = $before - DateTime::Duration->new( hours => $offset );
	    }
	    my $st = $dt->hires_epoch();
	    my $ft = $first->hires_epoch();
	    foreach my $str (@lines)
	    {
		next unless ($str);
		my ($val,$t) = split(/\t/,$str);
		push @newlines,$str if ($t > $ft);
	    }
	    my ($val,$t) = split(/\t/,$newlines[0]);
	    my ($lastval,$lasttime) = split(/\t/,$newlines[-1]);
	    if ($lasttime < $st)
	    {

		push @newlines,$value."\t".time();
	    }
	    $res = $val;
	}
	else
	{
	    push @newlines,$value."\t".time();
	    $res = $value;
	}
	$self->{'_file'}->writeline(@newlines);
	$self->{'_file'}->lock_un;
        $data->set($self->type,$self->name,$res);
        $self->writedebug("Measured value for ".$self->name.' was '.$res) if ($self->debug > 3); 
    }
    return $res;
}

sub print_config()
{
    my $self = shift;
    $self->init();
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Type: '.$self->type;
    push @arr,'Interval type: '.$self->{'_interval_type'};
    push @arr,'Interval: '.$self->{'_interval'};
    push @arr,'Offset: '.$self->{'_offset'};
    push @arr,$self->{'_dev'}->print_config();
    return join(', ',@arr);
}






package RFC::MovingAverage;
use RFC::Header;
our @ISA = qw(RFC::Lowpass);


sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Moving_average';
    $self->{'_setup_tags'} = ['class','mode','device_type','device','interval_type','interval','method','min','max','read_function','title','show_plot'];
    $self->{'_reload_tags'} = ['class','mode','device_type','device','interval_type'];
    $self->{'_setup_tags_readonly'} = ['mode'];
    $self->{'_desc'} = {
        'class' => 'Device class',
        'mode' => 'Device control mode',
        'device_type' => 'Type of raw device',
        'device' => 'Raw device',
	'interval' => 'Number of readings to aveage over',
	'method' => 'Averaging method, The median method often gives better noise rejection than simple arithmetic mean',
        'title' => 'Optional title',
	'interval_time' => 'Time interval in minutes for average calculation if interval_type is set to time interval',
	'interval_type' => 'Determines if interval is in number of measruements or a time interval',
	'min' => 'Only values above the min value will be considered for average calculation, if no min value is set, no minimum limit is enforced',
	'max' => 'Only values below the max value will be considered for average calculation, if no max value is set, no maximum limit is enforced',
        'read_function' => 'Device function to use for read operations',
    };
    $self->{'_default'} = {
        'class' => 'MovingAverage',
        'device_type' => 'simplechannel',
        'device' => '',
	'mode' => 'Readonly',
	'interval' => 5,
	'method' => 'Mean',
	'read_function' => 'read',
        'show_plot'=>'Yes',
	'interval_time' => 60,
	'interval_type' => 'Measurements',
    };
    bless $self,$class;
    
    $self->{'_values_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_values_method'} = ['Mean','Median'];
    $self->{'_values_interval_type'} = ['Measurements','Time interval'];
    $self->{'_values_interval'} = [3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
    $self->{'_readonly'} = 1;
    $self->{'_mode'} = 'Readonly';
    $self->load_conf();
    $self->{'_file'} = SemaforeFile->new($userdir.'rig'.$self->rig->name.'/Filter_'.$self->name,'/home/celltest/Gas_rig'.$self->rig->name.'.lock');
    unless ($self->{'_file'}->exist)
    {
        ### Create file with rw for all           ###
        ### Yes I know 666 is the number of evil! ###
        $self->{'_file'}->lock_ex;
        $self->{'_file'}->writeline('0.00');
        chmod 0666, $self->{'_file'}->filename;
        $self->{'_file'}->unlock;
    }
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
	push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    $self->{'_values_device'} = ['',@list];
    $self->{'_values_read_function'} = ['read'];
    if ($self->{'_device_type'} && $self->{'_device'})
    {
	### get device if it already exist ###
	my $dev = $self->rig->get_device($self->{'_device_type'},$self->{'_device'});
	$self->{'_dev'} = $dev if ($dev);	
    }
    if ($self->{'_interval_type'} && $self->{'_interval_type'} =~ /time/i)
    {
	$self->{'_setup_tags'} = ['class','mode','device_type','device','interval_type','interval_time','method','min','max','read_function','title','show_plot'];
	$self->load_conf();
    }
    return $self;
}

sub local_init
{
    my $self = shift;
    if (defined($self->{'_min'}) && defined($self->{'_max'}) && $self->{'_min'} >= $self->{'_max'})
    {
	$self->die("Minimum value must be less than maximum value!");
    }
    return $self;
}

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

sub test_string
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    my @arr = ();
    $self->init();
    my $string = 'Moving average filter '.$self->name.': ';
    my $raw = $self->device->read_named($self->{'_read_function'},$data);
    my $val = $self->read($data);
    push @arr,'Raw read: '.$raw;
    push @arr,'value: '.$val;
    $string .= join(', ',@arr)."\n";
    return $string;
}

sub read
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    my $res = 0;
    $self->check_cache($data);
    my $cached = $data->get($self->type,$self->name);
    if (defined($cached))
    {
        $self->writedebug("Using already measured value for ".$self->name) if ($self->debug > 3); 
        $res = $cached;
    }
    else
    {
	my $value = $self->device->read_named($self->{'_read_function'},@_);
	$self->{'_file'}->lock_ex();
	my @lines = $self->{'_file'}->readlines;
	push @lines,$value."\t".time();
	if ($self->{'_interval_type'} && $self->{'_interval_type'} =~ /time/i)
	{
	    my @newlines = ();
	    foreach my $l (@lines)
	    {
		my ($val,$t) = split(/\t/,$l);
		next if ($t < time - $self->{'_interval_time'} * 60);
		push @newlines,$l;
	    }
	    @lines = @newlines;
	}
	else {
	    while (@lines > $self->{'_interval'})
	    {
		shift @lines;
	    }
	}
	my @vals = ();
	foreach my $l (@lines)
	{
	    my ($val,$t) = split(/\t/,$l);
	    next if (defined($self->{'_min'}) && $val < $self->{'_min'});
	    next if (defined($self->{'_max'}) && $val > $self->{'_max'});
	    push @vals,$val;
	}
	if (@vals)
	{
	    if ($self->{'_method'} eq 'Median')
	    {
		my @arr = sort(@vals);
		my $index = POSIX::floor(scalar(@arr)/2);
		$res = $arr[$index];
	    }
	    else
	    {
		my $sum = 0;
		foreach (@vals)
		{
		    $sum += $_;
		}
		$res = $sum/@vals;
	    }
	}
	else
	{
	    $res = -32768;
	}
	$self->{'_file'}->writeline(@lines);
	$self->{'_file'}->lock_un();
        $data->set($self->type,$self->name,$res);
        $self->writedebug("Measured value for ".$self->name.' was '.$res) if ($self->debug > 3); 
    }
    return $res;
}

package RFC::CircularAverage;
use RFC::Header;
use Math::Complex;
our @ISA = qw(RFC::MovingAverage);


sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Circular_average';
    $self->{'_setup_tags'} = ['class','mode','device_type','device','interval_type','interval','data_type','read_function','title','show_plot'];
    $self->{'_reload_tags'} = ['class','mode','device_type','device','interval_type'];
    $self->{'_setup_tags_readonly'} = ['mode'];
    $self->{'_monitor_rows'} = '3,5';
    $self->{'_desc'} = {
        'class' => 'Device class',
        'mode' => 'Device control mode',
        'device_type' => 'Type of raw device',
        'device' => 'Raw device',
	'interval' => 'Number of readings to aveage over',
	'data_type' => 'Specify if data is in radians or degrees',
        'title' => 'Optional title',
        'read_function' => 'Device function to use for read operations',
	'interval_time' => 'Time interval in minutes for average calculation if interval_type is set to time interval',
	'interval_type' => 'Determines if interval is in number of measruements or a time interval',
    };
    $self->{'_default'} = {
        'class' => 'CircularAverage',
        'device_type' => 'simplechannel',
        'device' => '',
	'mode' => 'Readonly',
	'interval' => 5,
	'data_type' => 'Radian',
	'read_function' => 'read',
	'interval_time' => 60,
	'interval_type' => 'Measurements',
        'show_plot'=>'Yes',
    };
    bless $self,$class;
    $self->{'_values_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_values_data_type'} = ['Radian','Degree'];
    $self->{'_values_interval'} = [3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
    $self->{'_readonly'} = 1;
    $self->{'_mode'} = 'Readonly';
    $self->load_conf();
    $self->{'_file'} = SemaforeFile->new($userdir.'rig'.$self->rig->name.'/Filter_'.$self->name,'/home/celltest/Gas_rig'.$self->rig->name.'.lock');
    if ($self->{'_data_type'} =~ /deg/i)
    {
	$self->{'_info_string'} = 'Values will be reported in the interval [ 0 - 360 [';	
    }
    else
    {
	$self->{'_info_string'} = 'Values will be reported in the interval [ 0 - 2*pi [';	
    }
    unless ($self->{'_file'}->exist)
    {
        ### Create file with rw for all           ###
        ### Yes I know 666 is the number of evil! ###
        $self->{'_file'}->lock_ex;
        $self->{'_file'}->writeline('0.00');
        chmod 0666, $self->{'_file'}->filename;
        $self->{'_file'}->unlock;
    }
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
	push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    $self->{'_values_device'} = ['',@list];
    $self->{'_values_read_function'} = ['read'];
    if ($self->{'_device_type'} && $self->{'_device'})
    {
	### get device if it already exist ###
	my $dev = $self->rig->get_device($self->{'_device_type'},$self->{'_device'});
	$self->{'_dev'} = $dev if ($dev);	
    }
    if ($self->{'_interval_type'} && $self->{'_interval_type'} =~ /time/i)
    {
	$self->{'_setup_tags'} = ['class','mode','device_type','device','interval_type','interval_time','interval','data_type','read_function','title','show_plot'];
	$self->load_conf();
    }
    return $self;
}

sub monitor
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = shift;
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    $self->check_cache($data);
    my $val = $self->read($data);
    my $prec  = $self->{'_precision'};
    foreach ($val,$prec)
    {
        $_ = -32768 unless (defined($_) && /\d/);
    }
    return join(' ','Value',$val,'Precision',$prec);
}

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 $val = $self->read($data,@_);
    my $prec  = $self->{'_precision'};
    $prec = -32768 unless ($prec);
    my @t = $self->get_time();
    my $t = join(':',@t);
    my $res = join(' ',$self->name.'_value',$t,time,$val,$self->name.'_precision',$t,time,$prec);
    $self->writedebug($res) if ($self->debug > 1);
    return $res;
}


sub print_config()
{
    my $self = shift;
    $self->init();
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Type: '.$self->type;
    push @arr,'Data type: '.$self->{'_data_type'};
    push @arr,$self->{'_dev'}->print_config();
    return join(', ',@arr);
}


sub test_string
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    my @arr = ();
    $self->init();
    my $string = 'Circular average filter '.$self->name.': ';
    my $raw = $self->device->read_named($self->{'_read_function'},$data);
    my $val = $self->read($data);
    my $prec  = $self->{'_precision'};
    $prec = -32768 unless ($prec);    
    push @arr,'Raw read: '.$raw;
    push @arr,'value: '.$val;
    push @arr,'Precision: '.$prec;
    $string .= join(', ',@arr)."\n";
    return $string;
}

sub read
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    my $res = 0;
    $self->check_cache($data);
    my $cached = $data->get($self->type,$self->name);
    if (defined($cached))
    {
        $self->writedebug("Using already measured value for ".$self->name) if ($self->debug > 3); 
        $res = $cached;
    }
    else
    {
	my $value = $self->device->read_named($self->{'_read_function'},@_);
	$self->{'_file'}->lock_ex();
	my @lines = $self->{'_file'}->readlines;
	push @lines,$value."\t".time();
	if ($self->{'_interval_type'} && $self->{'_interval_type'} =~ /time/i)
	{
	    my @newlines = ();
	    foreach my $l (@lines)
	    {
		my ($val,$t) = split(/\t/,$l);
		next if ($t < time - $self->{'_interval_time'} * 60);
		push @newlines,$l;
	    }
	    @lines = @newlines;
	}
	else {
	    while (@lines > $self->{'_interval'})
	    {
		shift @lines;
	    }
	}
	my @vals = ();
	foreach my $l (@lines)
	{
	    my ($val,$t) = split(/\t/,$l);
	    push @vals,$val;
	}
	if (@vals)
	{
	    my $sum = undef;
	    my $n = 0;
	    foreach my $x (@vals)
	    {
		next unless ($x =~ /\d/);
		if ($self->{'_data_type'} =~ /degr/i)
		{
		    if (defined($sum))
		    {
			$sum += cplxe(1,$x * 2 * pi / 360);
		    }
		    else
		    {
			$sum = cplxe(1,$x * 2 * pi / 360);		    
		    }
		}
		else
		{
		    if (defined($sum))
		    {
			$sum += cplxe(1,$x);
		    }
		    else
		    {
			$sum = cplxe(1,$x);		    
		    }
		}
		$n++;
	    }
	    my $val = $sum / $n;
	    $res = arg($val);
	    $self->{'_precision'} = abs($val);
	    $res += 2 * pi if ($res < 0);
	    if ($self->{'_data_type'} =~ /degr/i)
	    {
		$res = $res * 180/pi;
	    }
	}
	$self->{'_file'}->writeline(@lines);
	$self->{'_file'}->lock_un();
        $data->set($self->type,$self->name,$res);
        $self->writedebug("Measured value for ".$self->name.' was '.$res) if ($self->debug > 3); 
    }
    return $res;
}


package RFC::Integrator;
use RFC::Header;
use Time::HiRes qw(time);
our @ISA = qw(RFC::Lowpass);


sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Integrator';
    $self->{'_setup_tags'} = ['class','mode','device_type','device','read_function','title','show_plot'];
    $self->{'_reload_tags'} = ['class','mode','device_type','device'];
    $self->{'_setup_tags_readonly'} = ['mode'];
    $self->{'_desc'} = {
        'class' => 'Device class',
        'mode' => 'Device control mode',
        'device_type' => 'Type of raw device',
        'device' => 'Raw device',
        'title' => 'Optional title',
        'read_function' => 'Device function to use for read operations',
    };
    $self->{'_default'} = {
        'class' => 'Integrator',
        'device_type' => 'simplechannel',
        'device' => '',
	'mode' => 'Readonly',
	'read_function' => 'read',
        'show_plot'=>'Yes',
    };
    bless $self,$class;
    $self->{'_values_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_readonly'} = 1;
    $self->{'_mode'} = 'Readonly';
    $self->{'_confidence'} = 0;
    $self->{'_info_string'} = 'Note that filter devices used in a control chain only works correctly if they are included (enabled). Values are reported in *hour!';
    $self->load_conf();
    $self->{'_file'} = SemaforeFile->new($userdir.'rig'.$self->rig->name.'/Filter_'.$self->name,'/home/celltest/Gas_rig'.$self->rig->name.'.lock');
    unless ($self->{'_file'}->exist)
    {
        ### Create file with rw for all           ###
        ### Yes I know 666 is the number of evil! ###
        $self->{'_file'}->lock_ex;
        $self->{'_file'}->writeline(time."\n0.00\n0.00");
        chmod 0666, $self->{'_file'}->filename;
        $self->{'_file'}->unlock;
    }
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
	push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    $self->{'_values_device'} = ['',@list];
    $self->{'_values_read_function'} = ['read'];
    if ($self->{'_device_type'} && $self->{'_device'})
    {
	### get device if it already exist ###
	my $dev = $self->rig->get_device($self->{'_device_type'},$self->{'_device'});
	$self->{'_dev'} = $dev if ($dev);	
    }
    $self->{'_set_fn_names'} = ['set_int']; # List of function names which can trigger a notify event
    $self->{'_ui_fn_names'} = ['set_int']; # List of function names which can be called from the device setup UI
    $self->{'_ui_fn_args'} = {'set_int' => 1}; # Hash with number of args for function listed in _ui_fn_names 
    $self->{'_ui_fn_desc'} = {
        'set_int' => 'Sets the integrated value to a specific value. 0 is assumed if no value is specified',
    };
    return $self;
}

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


sub test_string
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    my @arr = ();
    $self->init();
    my $string = 'Integrator filter '.$self->name.': ';
    my $raw = $self->device->read_named($self->{'_read_function'},$data);
    my $val = $self->read($data);
    push @arr,'Raw read: '.$raw;
    push @arr,'value: '.$val;
    $string .= join(', ',@arr)."\n";
    return $string;
}

sub read
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    my $res = 0;
    $self->check_cache($data);
    my $cached = $data->get($self->type,$self->name);
    if (defined($cached))
    {
        $self->writedebug("Using already measured value for ".$self->name) if ($self->debug > 3); 
        $res = $cached;
    }
    else
    {
	my $value = $self->device->read_named($self->{'_read_function'},@_);
	$self->{'_file'}->lock_ex();
	my ($old_time,$old_val,$integr) = $self->{'_file'}->readlines;
	my $t = time;
	if ($t > $old_time)
	{
	    ## Use trapezoid method and report in hour basis
	    $integr += ($old_val + $value) * ($t - $old_time ) / (2 * 3600);
	    $res = $integr;
	    $self->{'_file'}->writeline($t,$value,$integr);
	}
	$self->{'_file'}->lock_un();
        $data->set($self->type,$self->name,$res);
        $self->writedebug("Measured value for ".$self->name.' was '.$res) if ($self->debug > 3); 
    }
    return $res;
}


sub set_int
{
    my $self = shift;
    my $value = shift;
    $value = 0 unless ($value && $value =~ /\d+/);
    $self->{'_file'}->lock_ex();
    my $t = time;
    $self->{'_file'}->writeline($t,$value,$value);
    $self->{'_file'}->lock_un();
    return $self;
}


package RFC::Derivative;
use Statistics::Distributions;
use Statistics::Regression;
use Time::HiRes qw(time);
use RFC::Header;
our @ISA = qw(RFC::Lowpass);


sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Derivative';
    $self->{'_setup_tags'} = ['class','mode','device_type','device','interval_type','interval','unit','read_function','title','show_plot'];
    $self->{'_reload_tags'} = ['class','mode','device_type','device','interval_type'];
    $self->{'_setup_tags_readonly'} = ['mode'];
    $self->{'_desc'} = {
        'class' => 'Device class',
        'mode' => 'Device control mode',
        'device_type' => 'Type of raw device',
        'device' => 'Raw device',
	'interval' => 'Number of readings to iterate over',
	'unit' => 'Scaling factor',
        'title' => 'Optional title',
        'read_function' => 'Device function to use for read operations',
	'interval_time' => 'Time interval in minutes for calculation of the derivative if interval_type is set to time interval',
	'interval_type' => 'Determines if interval is in number of measruements or a time interval',
    };
    $self->{'_default'} = {
        'class' => 'Integration',
        'device_type' => 'simplechannel',
        'device' => '',
	'mode' => 'Readonly',
	'interval' => 5,
	'unit' => '/s',
	'read_function' => 'read',
        'show_plot'=>'Yes',
	'interval_time' => 60,
	'interval_type' => 'Measurements',
    };
    bless $self,$class;
    $self->{'_values_interval_type'} = ['Measurements','Time interval'];
    $self->{'_values_device_type'} = ['',$self->rig->list_types_nofilter()];
    $self->{'_values_interval'} = [3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
    $self->{'_values_unit'} = ['/s','/min','/hour'];
    $self->{'_scaling_factor'} = {'/s' => 1,'/min' => 60,'/hour'=> 3600};
    $self->{'_readonly'} = 1;
    $self->{'_mode'} = 'Readonly';
    $self->{'_confidence'} = 0;
    $self->load_conf();
    $self->{'_file'} = SemaforeFile->new($userdir.'rig'.$self->rig->name.'/Filter_'.$self->name,'/home/celltest/Gas_rig'.$self->rig->name.'.lock');
    unless ($self->{'_file'}->exist)
    {
        ### Create file with rw for all           ###
        ### Yes I know 666 is the number of evil! ###
        $self->{'_file'}->lock_ex;
        $self->{'_file'}->writeline(time."\t0.00");
        chmod 0666, $self->{'_file'}->filename;
        $self->{'_file'}->unlock;
    }
    my @list = ();
    foreach ($self->rig->avaliable_devices($self->{'_device_type'}))
    {
	push @list,$_ unless ($_ eq $self->name()); ## Do not include self ##
    }
    $self->{'_values_device'} = ['',@list];
    $self->{'_values_read_function'} = ['read'];
    if ($self->{'_device_type'} && $self->{'_device'})
    {
	### get device if it already exist ###
	my $dev = $self->rig->get_device($self->{'_device_type'},$self->{'_device'});
	$self->{'_dev'} = $dev if ($dev);	
    }
    if ($self->{'_interval_type'} && $self->{'_interval_type'} =~ /time/i)
    {
	$self->{'_setup_tags'} = ['class','mode','device_type','device','interval_type','interval_time','unit','read_function','title','show_plot'];
	$self->load_conf();
    }
    return $self;
}



sub get_confidence
{
    my $self = shift;
    return sprintf("%.6g",$self->{'_confidence'} * $self->{'_scaling_factor'}{$self->{'_unit'}});
}


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

sub test_string
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    $self->check_cache($data);
    my @arr = ();
    $self->init();
    my $string = 'Derivative filter '.$self->name.': ';
    my $raw = $self->device->read_named($self->{'_read_function'},$data);
    my $val = $self->read($data);
    push @arr,'Raw read: '.$raw;
    push @arr,'value: '.$val;
    my $min = $val - $self->get_confidence();
    my $max = $val + $self->get_confidence();
    push @arr,'Confidence: ['.$min.' , '.$max.']';
    $string .= join(', ',@arr)."\n";
    return $string;
}

sub read
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug) if ($self->debug);
    }
    my $res = 0;
    $self->check_cache($data);
    my $cached = $data->get($self->type,$self->name);
    if (defined($cached))
    {
        $self->writedebug("Using already measured value for ".$self->name) if ($self->debug > 3); 
        $res = $cached;
    }
    else
    {
	my $value = $self->device->read_named($self->{'_read_function'},@_);
	$self->{'_file'}->lock_ex();
	my @lines = $self->{'_file'}->readlines;
	push @lines,time()."\t".$value;
	if ($self->{'_interval_type'} && $self->{'_interval_type'} =~ /time/i)
	{
	    my @newlines = ();
	    foreach my $l (@lines)
	    {
		my ($t,$val) = split(/\t/,$l);
		next if ($t < time - $self->{'_interval_time'} * 60);
		push @newlines,$l;
	    }
	    @lines = @newlines;
	}
	else {
	    while (@lines > $self->{'_interval'})
	    {
		shift @lines;
	    }
	}
        my $reg=Statistics::Regression->new("Title", ["Intercept", "Slope"]);
        for my $line (@lines)
        {
            next unless ($line);
            my ($x,$y) = split(/\t/,$line);
	    next unless (defined($x) && defined($y));
            $reg->include($y, [1.0, $x]);
        }
        eval{
            my @stat = $reg->theta();
            $res = $stat[1];
            my @conf = $reg->standarderrors();
            $self->{'_confidence'} = $conf[1];
	};
        if ($@)
	{
            $self->{'_confidence'} = -32768;
	}
	$self->{'_file'}->writeline(@lines);
	$self->{'_file'}->lock_un();
        $data->set($self->type,$self->name,$res);
        $self->writedebug("Measured value for ".$self->name.' was '.$res) if ($self->debug > 3); 
    }
    return sprintf("%.4g",$res * $self->{'_scaling_factor'}{$self->{'_unit'}});
}

return 1;



