package RFC::Pcontrol;
#$Id: Pcontrol.pm,v 1.19 2025/06/16 07:22:16 sqko Exp $
####################################################################
#
#
#     RFC::Pcontrol
#
# This class defines the behaviour of pressure controlers for gas
# control. To obtain a Pcontrol instance call one of the constructors
# as shown below:
#
# $id = RFC::Pcontrol->new($name,$rig);
#
# $id = RFC::Pcontrol_Analog->new($name,$rig);
#
# $id = RFC::Pcontrol_AnalogReadonly->new($name,$rig);
#
# $id = RFC::Pcontrol_ER3000->new($name,$rig);
#
# The RFC::Pcontrol device is the default manual device.
#
#
# All RFC::Pcontrol devices emulates the RFC::MFC interface (refer the RFC::MFC.pm
# module for further documentation of the special member functions 
# for RFC::MFC devices).
# This makes it possible to use a pressure control device as was it 
# a MFC device and thus a gas device can be used to control gas flow or
# gas pressure.
# In order to distinguish, Pcontrol devices allow only the selection
# of gas device names where the name contains the string 'pres'.
# This forces a more intuitive user interface where a clear distinction 
# between normal gas devices and gas pressure devices.
#
# RFC::Pcontrol devices does not support the addition of gas multiplexers,
# however to conform with the RFC::MFC interface, the functions to manipulate
# gas multiplexers are defined (but usually simply return undef).
#
#
# All calculations for pressures using Pcontrol devices are performed in 
# bar absolut (barA, 1 barA coresponds roughly to atmospheric pressure and
# 0 barA is a perfect vaccum).
#
# The RFC::Pcontrol class defines the following membeer functions: 
# 
#
# $id->minP             Returns the minimum pressure for the controler in barA
# $id->maxP             Returns the maximum pressure for the controler in barA
# $id->setP($[$][$])    Sets the gas pressure to the specified value
#                       If an additional argument is specified, it is assumed to be
#                       the username of the controlling user and that name is appended
#                       to the string appended to the rigs proglog.
#                       an exception to this is if a further argument is specified in 
#                       which case no proglog entry is appended! 
# $id->set              Alias for $id->setP.
#
# The following RFC::MFC member function has been remapped:
#
# $id->setflow          Alias for $id->setP.
# $id->maxflow          Alias for $id->maxP.
# $id->gasses           Returns a single element ($id->selected_gas).
#
#
####################################################################


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


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


sub new()
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'_mode'} = 'Manual';
    $self->{'_type'} = 'Pc_Manual';
    $self->{'_setup_tags'} = ['mode','minpressure','maxpressure','gas','title','show_plot','use_cache'];
    $self->{'_accuracy'} = 0.01;
    $self->{'_desc'} = {
        'mode' => 'Device control mode',
        'minpressure' => 'Minimum pressure (Note in barA, 1 BarA is atmospheric pressure!)',
        'maxpressure' => 'Maximum pressure (Note in barA, 1 barA is atmospheric pressure!)',
        'gas' => 'selected gas',
        'title' => 'Optional title',
        'use_cache'=>'Determines if persisten caching of read values is allowed',
    };
    $self->{'_set_fn_names'} = ['setflow','setP']; # List of function names which can trigger a notify event
    $self->{'_values_mode'} = ['Manual'];
    $self->{'_default'} = {
        'gas' => 'n2',
        'mode' => 'Manual',
	'minpressure' => 1,
	'maxpressure' => 101,
        'use_cache'=>'No',
    };
    $self->{'_values_mode'} = ['Manual'];
    $self->{'_set_names'} = ['setP','setflow','set'];
    $self->{'_values_gas'} = ['',$self->rig->avaliable_devices('gas')];
#    my @list = ();
#    foreach my $name ($self->rig->avaliable_devices('gas'))
#    {
#	push @list,$name if ($name && $name =~ /pres/i);
#    }
#    $self->{'_values_gas'} = ['',@list];
    $self->{'_file'} = SemaforeFile->new($userdir.'rig'.$self->rig->name.'/MFC_'.$self->name,'/home/celltest/Gas_rig'.$self->rig->name.'.lock');
    unless (-e $self->{'_file'}->filename)
    {
        ### 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->{'_section'} = 'controler_'.$self->name;
    $self->{'_controler_mode'} = 'Pressure';
    bless $self,$class;
    return $self;
}

sub include_tally()
{
    my $self = shift;
    ## Pressure can never be tallied for use...
    return 0;
}


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

sub controler_mode()
{
    my $self = shift;
    return $self->{'_controler_mode'};
}

sub accuracy()
{
    my $self = shift;
    return $self->{'_accuracy'} * ($self->{'_maxpressure'} - $self->{'_minpressure'});
}

sub selected_gas()
{
    my $self = shift;
    return $self->{'_gas'};
}

sub gasses()
{
    my $self = shift;
    return ($self->{'_gas'});
}

sub gas_change()
{
    my $self = shift;
    return 'Manual';
}

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

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

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

sub init
{
    my $self = shift;
    ### Stub function, overload fro derived classes ###
    return $self;
}

sub maxflow()
{
    my $self = shift;
    return $self->{'_maxpressure'};
}

sub maxP()
{
    my $self = shift;
    return $self->{'_maxpressure'};
}

sub minP()
{
    my $self = shift;
    return $self->{'_minpressure'};
}

sub read
{
    my $self = shift;
    my $flow = 0;
    my $data = RFC::Cache->new();
    $data = shift if (@_);
    my $var = $data->get('Pcontrol',$self->name);
    return $var if (defined($var));
    $flow = $self->read_nocache($data);
    $data->set('Pcontrol',$self->name,$flow);
    return $flow;
}

sub read_set
{ 
    my $self = shift;
    my @lines = $self->{'_file'}->readlines;
    my $p = $lines[0] if (@lines);
    return $p;
}

sub read_nocache
{
    my $self = shift;
    my $flow = $self->read_set();
    $self->{'_last_read'} = Time::HiRes::time;
    ### Stub function, override in derived classes ###
    return $flow;
}

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

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

sub setP($)
{
    my $self = shift;
    my $flow = shift;
    $flow = $self->maxP if ($flow > $self->maxP);
    $flow = $self->minP if ($flow < $self->minP);
    $self->{'_file'}->writeline(sprintf("%.4f",$flow));
    ### Stub function, override in derived classes ###
    $self->proglog("Pressure for gas ".$self->{'_gas'}." set to $flow barA manually",@_);
    $self->notify('setflow',$flow,@_);
    $self->notify('setP',$flow,@_);
    return $flow;
}

sub change_gas($)
{
    my $self = shift;
    my $gas = shift;
    $self->die("Gas $gas not found as avaliable for controler ".$self->name) unless ($gas eq $self->{'_gas'});
    return 'Gas for pressure controler '.$self->name.' must be changed manually!';
}


package RFC::Pcontrol_ER3000;
our @ISA = qw(RFC::Pcontrol);
sub new()
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'_mode'} = 'Automatic';
    $self->{'_readonly'} = 0;
    $self->{'_type'} = 'Pc_ER3000';
    $self->{'_tty'} = undef;
    $self->{'_address'} = 0;
    $self->{'_setup_tags'} = ['mode','tty','address','minpressure','maxpressure','gas','title','show_plot','use_cache'];
    $self->{'_desc'} = {
        'mode' => 'Device control mode',
	'tty' => 'Communication device (ex. ttyS0)',
	'address' => 'Device address (Default factory address on a ER3000 is 250)',
        'minpressure' => 'Minimum pressure (Note in barA, 1 BarA is atmospheric pressure!)',
        'maxpressure' => 'Maximum pressure (Note in barA, 1 barA is atmospheric pressure!)',
        'gas' => 'selected gas',
        'title' => 'Optional title',
        'show_plot'=>'Determines if the current device data is to be shown in the daily data plots',
        'use_cache'=>'Determines if persisten caching of read values is allowed',
    };
    $self->{'_values_mode'} = ['Automatic'];
    $self->{'_default'} = {
        'gas' => 'n2',
        'mode' => 'Automatic',
	'minpressure' => 1,
	'maxpressure' => 101,
        'show_plot'=>'Yes',
        'use_cache'=>'No',
	'tty' => 'ttyS0',
	'address' => 1,
    };
    bless $self,$class;
    $self->{'_accuracy'} = sprintf("%.4f",2/3300);
    return $self->init;
}


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/);
    }
    return $self;
}

sub sendcmd
{
    my $self = shift;
    my @args = @_;
    my $res = SocketClient::serial_client_raw($self->{'_tty'},'tescom',$self->{'_address'},@args);
    unless (defined($res))
    {
	$self->writedebug("No response from ER3000 ".$self->name.", retrying");
	select(undef,undef,undef,0.1);
	$res = SocketClient::serial_client_raw($self->{'_tty'},'tescom',$self->{'_address'},@args);
    }
    unless (defined($res))
    {
	my $str = "Error: ER3000 device '".$self->name."' on tty ".$self->{'_tty'}." did not respond!";
	$self->rig->warning_mail($self,$str);
        return undef;
    }	
    return $res;
}

sub test()
{
    my $self = shift;
    my $val = $self->sendcmd('r',52); 
    return 1 if (defined($val) && $val);
    return 0;
}

sub test_string()
{
    my $self = shift;
    my $id = $self->sendcmd('r',52);
    my $flow = $self->read(@_);
    my $string = "Pressure for controler ".$self->name." with id '$id' was $flow L/hour\n";
    return $string;
}

sub read_nocache
{
    my $self = shift;
    my $flow = 0;
    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('ER3000',$self->{'_tty'},$self->{'_address'});
    if (defined($cached))
    {
        $self->writedebug("Using already cached pressure value for ER3000 ".$self->name) if ($self->debug);
        return $cached;
    }
    my $p = $self->sendcmd('r',44);
    unless ($p)
    {
	$flow = -32768;
    }
    else
    {
	$p = ($p - 400)/3300;
	$flow = $p * ($self->maxP - $self->minP) + $self->minP;
	$flow = sprintf("%.2f",$flow);
    }
    $data->set('ER3000',$self->{'_tty'},$self->{'_address'},$flow);
    $self->{'_last_read'} = Time::HiRes::time;
    $self->set_cache($flow);
    return $flow;
}

sub setP
{
    my $self = shift;
    my $flow = shift;
    $flow = $self->maxP if ($flow > $self->maxP);
    $flow = $self->minP if ($flow < $self->minP);
    $self->{'_file'}->writeline(sprintf("%.4f",$flow));
    my $lflow = ($flow - $self->minP) / ($self->maxP - $self->minP); 
    my $val = POSIX::floor($lflow * 3300 + 0.5) + 400;
    $self->writedebug("Setting pressure to $flow for gas ".$self->{'_gas'}." using ER3000 with address ".$self->{'_address'}." on tty ".$self->{'_tty'});
    my $res = '';
    eval {
	$res = $self->sendcmd('w',37,$val);
    };
    if ($@ || $res !~ /OK/i)
    {
	$self->rig->errorlog("Error: Gas pressure for controler ".$self->name." (".$self->{'_gas'}.") could not be set to $flow! Error: ".$@,@_);
	return undef;
    }
    $self->proglog("Pressure for gas ".$self->{'_gas'}." set to $flow barA",@_);
    $self->notify('setflow',$flow,@_);
    $self->notify('setP',$flow,@_);
    $self->rig->unset_warning($self);
    return $flow;
}



package RFC::Pcontrol_Analog;
our @ISA = qw(RFC::Pcontrol);
sub new()
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'_mode'} = 'Automatic';
    $self->{'_readonly'} = 0;
    $self->{'_type'} = 'Pc_Analog';
    $self->{'_values_output_range'} = ['0-5V','1-5V','0-10V','2-10V','0-20mA','4-20mA'];
    $self->{'_values_range'} = ['0-5V','1-5V','0-10V','2-10V','0-20mA','4-20mA'];
    $self->{'_input_name'} = undef;
    $self->{'_output_name'} = undef;
    $self->{'_input_device'} = undef;
    $self->{'_output_device'} = undef;
    $self->{'_setup_tags'} = ['mode','range','input_name','output_name','output_range','minpressure','maxpressure','gas','title','show_plot','use_cache'];
    $self->{'_desc'} = {
        'mode' => 'Device control mode',
	'range' => 'Input analog range',
	'output_range' => 'Output analog range',
	'input_name' => 'Input device name',
	'output_name' => 'Output device name',
        'minpressure' => 'Minimum pressure (Note in barA, 1 BarA is atmospheric pressure!)',
        'maxpressure' => 'Maximum pressure (Note in barA, 1 barA is atmospheric pressure!)',
        'gas' => 'selected gas',
        'title' => 'Optional title',
        'show_plot'=>'Determines if the current device data is to be shown in the daily data plots',
        'use_cache'=>'Determines if persisten caching of read values is allowed',
    };
    $self->{'_values_mode'} = ['Automatic'];
    $self->{'_default'} = {
        'gas' => 'n2',
	'range' => '0-5V',
	'output_range' => '0-5V',
        'mode' => 'Automatic',
	'minpressure' => 1,
	'maxpressure' => 101,
        'show_plot'=>'Yes',
        'use_cache'=>'No',
	'input_name' => '',
	'output_name' => '',
    };
    bless $self,$class;
    $self->{'_values_input_name'} = ['',$self->rig->avaliable_devices('simplechannel')];
    $self->{'_values_output_name'} = ['',$self->rig->avaliable_devices('analog')];
    $self->init;
    return $self;
}

sub test()
{
    my $self = shift;
    $self->init_output() unless ($self->{'_input_device'});
    return 0 unless($self->{'_input_device'}->test());
    return 0 unless($self->{'_output_device'}->test());
    return 1;
}

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


sub test_string()
{
    my $self = shift;
    $self->init_output() unless ($self->{'_output_device'});
    my $flow = $self->read(@_);
    my $string = "Pressure for controler ".$self->name." was $flow L/hour\n";
    $string .= 'Input device: '.$self->{'_input_device'}->test_string(@_)."\n";
    $string .= 'Output device: '.$self->{'_output_device'}->test_string(@_);
    return $string;
}

sub init_output()
{
    my $self = shift;
    my $dev = $self->rig->get_device('analog',$self->{'_output_name'});
    unless ($dev)
    {
        $dev = $self->rig->init_device('analog',$self->{'_output_name'});
        $dev->readstring_ignore(1) if ($dev);
    }
    $self->die("No AnalogOut device named '".$self->{'_output_name'}."' returned by rig ".$self->rig->name) if (!$dev && $self->{'_output_name'});
    $self->{'_output_device'} = $dev;
    return $self;
}

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/);
    }
    my $dev = $self->rig->get_device('simplechannel',$self->{'_input_name'});
    unless ($dev)
    {
        $dev = $self->rig->init_device('simplechannel',$self->{'_input_name'});
        $dev->readstring_ignore(1) if ($dev);
    }
    $self->die("No simplechannel device named '".$self->get_conf('channel_name')."' returned by rig ".$self->rig->name) if (!$dev && $self->{'_input_name'});
    $self->{'_input_device'} = $dev;
    $self->init_output();
    return $self;
}

sub read_nocache
{
    my $self = shift;
    my $flow = 0;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    $self->check_cache($data);
    my $volt = $self->{'_input_device'}->read($data);
    $self->writedebug("No data found for MFC ".$self->name.' for rig '.$self->rig->name.', assuming zero flow!') unless (defined($volt));
    if ($self->{'_range'} =~ /1-5V/i)
    {
	$flow = ($volt-1) / 4;
    }
    elsif ($self->{'_range'} =~ /0-5V/i)
    {
	$flow = $volt / 5;
    }
    elsif ($self->{'_range'} =~ /0-10V/i)
    {
	$flow = $volt / 10;
    }
    elsif ($self->{'_range'} =~ /2-10V/i)
    {
	$flow = ($volt-2) / 8;
    }
    elsif ($self->{'_range'} =~ /4-20mA/i)
    {
	$flow = ($volt-4) / 16;
    }
    elsif ($self->{'_range'} =~ /0-20mA/i)
    {
	$flow = $volt / 20;
    }
    else
    {
	$self->die("Unknown range ".$self->{'_range'});
    }
    $flow = $flow * ($self->maxP - $self->minP) + $self->minP;
    $self->writedebug("Flow for ".$self->name." was $flow") if ($self->debug > 1);
    my $str = time .' Pcontrol_'.$self->name.' '.$flow;
    $self->rig->kanaldata($str) unless ($self->readstring_ignore);
    $self->set_cache($flow);
    $self->{'_last_read'} = Time::HiRes::time;
    return $flow;
}


sub setP($)
{
    my $self = shift;
    my $flow = shift;
    $self->init_output() unless ($self->{'_output_device'});
    $flow = $self->maxP if ($flow > $self->maxP);
    $flow = $self->minP if ($flow < $self->minP);
    $self->{'_file'}->writeline(sprintf("%.4f",$flow));
    my $volt = 0;
    my $lflow = ($flow - $self->minP) / ($self->maxP - $self->minP); 
    if ($self->{'_range'} =~ /1-5V/i)
    {
        $volt = $lflow * 4 + 1;
    }
    elsif ($self->{'_range'} =~ /0-5V/i)
    {
        $volt = $lflow * 5;
    }
    elsif ($self->{'_range'} =~ /0-10V/i)
    {
        $volt = $lflow * 10;
    }
    elsif ($self->{'_range'} =~ /2-10V/i)
    {
        $volt = $lflow * 8 + 2;
    }
    elsif ($self->{'_range'} =~ /4-20mA/i)
    {
        $volt = $lflow * 16 + 4;
    }
    elsif ($self->{'_range'} =~ /0-20mA/i)
    {
        $volt = $lflow * 20;
    }
    else
    {
        $self->die("Unknown range ".$self->{'_range'});
    }
    if ($self->{'_output_name'})
    {
        $self->writedebug("Setting pressure to $flow for gas ".$self->{'_gas'}." using analog perssure controler controlled by ".$self->{'_output_device'}->name);
    }
    my $res = $self->{'_output_device'}->set($volt,@_);
    $self->writedebug($res) if ($self->debug > 1);
    $self->proglog("Pressure for gas ".$self->{'_gas'}." set to $flow barA",@_);
    $self->notify('setflow',$flow,@_);
    $self->notify('setP',$flow,@_);
    return $flow;
}

sub print_config()
{
    my $self = shift;
    $self->init_output() unless defined($self->{'_output_device'});
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Pcontrol type: '.$self->type;
    push @arr,'Mode: '.$self->mode;
    push @arr,$self->{'_output_device'}->print_config if ($self->{'_output_device'});
    push @arr,$self->{'_input_device'}->print_config;
    return join (', ',@arr);
}


package RFC::Pcontrol_AnalogReadonly;
our @ISA = qw(RFC::Pcontrol_Analog);
sub new()
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'_mode'} = 'Automatic';
    $self->{'_readonly'} = 1;
    $self->{'_type'} = 'Pc_AnalogReadonly';
    $self->{'_values_output_range'} = ['0-5V','1-5V','0-10V','2-10V','0-20mA','4-20mA'];
    $self->{'_input_name'} = undef;
    $self->{'_output_name'} = undef;
    $self->{'_input_device'} = undef;
    $self->{'_output_device'} = undef;
    $self->{'_setup_tags'} = ['mode','input_name','output_range','minpressure','maxpressure','gas','title','show_plot','use_cache'];
    bless $self,$class;
    $self->init;
    return $self;
}

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

sub test_string()
{
    my $self = shift;
    my $flow = $self->read(@_);
    my $string = "Pressure for controler ".$self->name." was $flow L/hour\n";
    $string .= 'Input device: '.$self->{'_input_device'}->test_string(@_)."\n";
    return $string;
}

sub setP
{
    my $self = shift;
    my $flow = shift;
    $flow = $self->maxP if ($flow > $self->maxP);
    $flow = $self->minP if ($flow < $self->minP);
    $self->errorlog("Pressure for gas ".$self->{'_gas'}." can not be set as ".$self->name." is a readonly device!",@_);
    $self->{'_file'}->writeline(sprintf("%.4f",$flow));
    return $flow;
}


return 1;
