package RFC::VacuumControl;
#$Id: VacuumControl.pm,v 1.6 2018/06/22 09:10:32 sqko Exp $

####################################################################
#
#
#     RFC::VacuumControl
#
# This class implements RFC::VacuumControl for pfeiffer-vacuum PID 
# controller RVC 300
# To obtain a VacuumControl instance call one of the constructors as
# shown below:
#
# $id = RFC::VacuumControl->new($name,$rig);
#
#
# The RFC::VacuumControl class 
#
#
# 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'} = 'Vacuumcontrol';
    $self->{'_setup_tags'} = ['mode','minpressure','maxpressure','gas','title','show_plot','use_cache'];
    $self->{'_accuracy'} = 0.001;	# 1 % of scale 
    $self->{'_desc'} = {
        'mode' => 'Device control mode',  # maybe select presure or flow control mode
        'minpressure' => 'Minimum pressure (Note in  mbarl/s,)',
        'maxpressure' => 'Maximum pressure (Note in mbarl/s )',
        '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'];  # not sure what it is
    $self->{'_default'} = {
        'gas' => 'n2',
        'mode' => 'Manual',
	'minpressure' => 0.001,		### get this info from controller 
	'maxpressure' => 1.1,
        'use_cache'=>'No',
    };
	
	
    $self->{'_values_mode'} = ['Manual'];
    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)
    {
		### not sure if file is needed for anything else than debugging 
        ### 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 DESTROY
{
    my $self = shift;
    $self->{'_file'}->DESTROY() if ($self->{'_file'});
    $self->{'_file'} = undef;
    return undef;
}

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

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

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

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 accuracy()
{
    my $self = shift;
    return $self->{'_accuracy'} * ($self->{'_maxpressure'} - $self->{'_minpressure'});
}

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("Vacuum Pressure for gas ".$self->{'_gas'}." set to $flow mbar l/s 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::VacuumControl_RVC300;
our @ISA = qw(RFC::VacuumControl);
sub new()
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
	
	
    $self->{'_mode'} = 'Automatic';
    $self->{'_type'} = 'VacuumControl_RVC300';
    $self->{'_tty'} = 'ttyS0'; #undef;
	$self->{'_accuracy'} = 0.001;
	$self->{'_setup_tags_readonly'} = ['Controllertype','Kp','Tn','Tv'];
    $self->{'_setup_tags'} = ['mode','tty','minpressure','maxpressure','gas','title','show_plot','use_cache','Controllertype','Kp','Tn','Tv'];
    $self->{'_desc'} = {
        'mode' => 'Device control mode',
		'tty' => 'Communication device (ex. ttyS0)',
	    'minpressure' => 'Minimum pressure (Note in barA = mbar /1000!)',
        'maxpressure' => 'Maximum pressure (Note in barA = mbar /1000!)',
        'gas' => 'selected gas',
		'Controllertype' => 'Controller type setup: a number between: (1-99) = PI and a 0 = PID ',
        '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',
		'Kp'=>'Gain Kp , Kontroller gain setting between 0.1 and 100.0',
		'Tn'=>'Reset time between 0.0 and 3600.0 s.',
		'Tv'=>'Derivative time between 0.0 and 3600.0 s.',
    };
    $self->{'_values_mode'} = ['Automatic'];
    $self->{'_default'} = {
        'gas' => 'n2',
        'mode' => 'Automatic',
		'minpressure' => 0.001,  	# barA  see if setting can be retained from controller 
		'maxpressure' => 1.1,	#barA
        'show_plot'=>'Yes',
        'use_cache'=>'No',
		'tty' => 'ttyS0',
	};
	
		### function run setKp
	$self->{'_set_fn_names'} = ['setflow','setP']; # List of function names which can trigger a notify event
    $self->{'_ui_fn_names'} = ['get_settings','set_control_type','setKp','setTn','setTv']; # List of function names which can be called from the device setup UI
    $self->{'_ui_fn_args'} = {'set_control_type' => 1,'setKp' => 1,'setTn' => 1,'setTv' => 1 }; # Hash with number of args for function listed in _ui_fn_names 
    $self->{'_ui_fn_desc'} = {
	'get_settings' => 'get the configuration settings from the EFA PID controller etc. Unit sensor and valve type. range and so on',
	'set_control_type' => 'set the control algoritme number between: 1(slow) and 99(fast) i auto mode(PI)  if 0 i selected the controller runs a a PID controller ',
	'setKp' => ' Sets the Kp constant in the vacuum controller', 
	'setTn' => ' Sets the Kp constant in the vacuum controller', 
	'setTv' => ' Sets the Kp constant in the vacuum controller', 
	};
	
    bless $self,$class;
    
    return $self->init;
	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/);
    }
    return $self;
}


sub sendcmd
{
    my $self = shift;
    my @args = @_;
    my $res = SocketClient::serial_client_raw($self->{'_tty'},'icp_raw',@args);
    unless (defined($res)) # we try again
    {
		$self->writedebug("No response from RVC300 ".$self->name.", retrying");
		select(undef,undef,undef,0.1);
		$res = SocketClient::serial_client_raw($self->{'_tty'},'icp_raw',@args);
    }
	unless (defined($res)) # transaction failed
    {
		my $str = "Error: RVC300 device '".$self->name."' on tty ".$self->{'_tty'}." did not respond!";
		$self->rig->warning_mail($self,$str);
        return undef;
    }
	if($res =~ m/ERROR-INPUT/){ # we try again to see if miss communication gave an "ERROR-INPUT"
		
		select(undef,undef,undef,0.1);
		$res = SocketClient::serial_client_raw($self->{'_tty'},'icp_raw',@args);  # return $res if not defined 
	}
	
    return $res;
}

sub test()
{
    my $self = shift;
    my $val = $self->sendcmd('VER?'); #  also ask for control mode  perhaps also PID setting etc.
    return 1 if (defined($val) && $val);
    return 0;
}

sub test_string()
{
    my $self = shift;
	
	my $string = "testing  ".$self->name."\n";
	$string = $string . "Setpoint= " . $self->getsetpoint() ."\n";
    $string = $string . "Actual pressure= " . $self->read_nocache() ."\n";
	$string = $string . "Control type setting: " . $self->getControlTypeSetting() ;  
    $string = $string . " unit: " . $self->getUNIT() ."\n";
	$string = $string . " " . $self->getMode();
    return $string;
}



sub read_nocache
{
    my $self = shift;
    my $pressure = 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('RVC300',$self->{'_tty'},$self->{'_address'});
    if (defined($cached))
    {
        $self->writedebug("Using already cached vacuum pressure value for RVC300 ".$self->name) if ($self->debug);
        return $cached;
    }
    my $p = $self->getValue('PRI?');		# gets actual value 
    unless ($p)
    {
		$pressure = -32768;
    }
    else
    {
		# if sensor is responding voltage
		# then remove extension and convert/scale to mbar from range and output value etc out: 0 -1000mV  range: 0 -1250mBar 
		# can be done with a spline function 	
		
		if($p =~ /mV/i)
		{	
			(my $mv)  = $p =~ /(\d+)/;
		
		}
		else #else remove extension and return a numeric value 
		{
			# remove 
			my @rmextension = ('mbar','Pa','torr','mV');
			foreach(@rmextension){
				$p =~ s/$_//; 		### should perhaps be none case sensitive 	
			}
			$p =~ s/PRI=//;			
			$pressure = sprintf("%.2f",$p)/ 1000 ;   # and scale to barA
		}
	
    }
    
	$self->{'_last_read'} = Time::HiRes::time;
    $self->set_cache($pressure);
    return $pressure;
}

sub read_set
{ 
    my $self = shift;
    
    return $self->getsetpoint()/1000;
}

sub setP($)	# barA rapper for setPrs
{
	my $self = shift;
    my $prs = shift;
	$prs = $self->maxP if ($prs > $self->maxP);
    $prs = $self->minP if ($prs < $self->minP);
	$self->{'_file'}->writeline(sprintf("%.3f",$prs));
	$self->writedebug("Setting pressure to $prs for gas ".$self->{'_gas'}." using RVC300 "." on tty ".$self->{'_tty'});
	
	$prs = $self->setPrs($prs * 1000) /1000;
	$self->proglog("Pressure for gas ".$self->{'_gas'}." set to $prs mBar",@_);
    $self->notify('setflow',$prs,@_);
    $self->notify('setP',$prs,@_);
    $self->rig->unset_warning($self);
    return $prs;
	
	 
}


sub setPrs($)	# takes arg in mBar
{

# PRS=x.xxEsxx 
    my $self = shift;
    my $prs = shift;
    
	   
    my $res = '';
	my $str = "PRS=" . sprintf("%.2E",$prs); 
    eval {
		$res = $self->sendcmd($str);
    };
    if ($@ || $res =~ m/$str/)
    {
		print " setpoint fail to set prs  $res \n   ";
		$self->rig->errorlog("Error: Gas pressure for controler ".$self->name." (".$self->{'_gas'}.") could not be set to " . $prs/1000 . "! Error: ".$@,@_);
		return undef;
    }
    return $prs;
}

sub setmode()
{	
	# PRESS = pressure control mode , FLOW = flow  control mode,   WAIT = puts the controller on hold 
	my $self = shift;
    my $mode = shift;
	my $res = '';
	my $str = "MOD=" . $mode;
	
	$self->writedebug("Setting control mode to $mode for gas ".$self->{'_gas'}." on PID controller RVC300 "." on tty ".$self->{'_tty'});
    eval {
	$res = $self->sendcmd($str);
    };
    if ($@ || $res !~ /$str/i)
    {
		$self->rig->errorlog("Error: controler ".$self->name." (".$self->{'_gas'}.") could not be set to mode $mode ! Error: ".$@,@_);
		return undef;
    }
	
	$self->proglog("controler mode for ".$self->{'_gas'}." set to $mode ",@_);
    
    $self->rig->unset_warning($self);
    return $mode;
	
}

sub getsetpoint()
{
	my $self = shift;
   	my $res = $self->getValue("PRS?");		# will also need  "FLO?"  if class should handle flow control 
	# perhaps validate if a defined eler return some number -32768 
	my @rmextension = ('mbar','Pa','torr','mV');
	foreach(@rmextension){
		$res =~ s/$_//; 		### should perhaps be none case sensitive 	
	}
	$res =~ s/PRS=//;	
	
	return( sprintf("%.2f",$res));   # 

}

sub getUNIT()
{
	my $self = shift;
    my $res = '';
	$res = $self->getValue("UNT?");
	
	my @rmextension = ('mbar','Pa','torr','mV');
	
	foreach(@rmextension)
	{
		if ($res =~ /$_/) { return $_; } # returns extension 
	}
	return undef; # default "mbar" or undef	
	
}
sub getSensorType()
{
	my $self = shift;
   	return $self->getValue("RTP?");
}

sub getControlTypeSetting()
{
	my $self = shift;
	my $res = $self->getValue("RAS?");
	$res =~ s/RAS=//;	
	return $res;
	
   	 
}

sub getMode()
{
	my $self = shift;
   	return $self->getValue("MOD?");
}

sub getValue()
{
    my $self = shift;
    my $cmd = shift;	# controller retain CMD  etc "RTP?"
    
    my $res = '';
    
    eval {
	$res = $self->sendcmd($cmd);
    };
    
    if ($@ || ($res && $res =~ m/ERROR-INPUT/ ))			# checking if responds is valid   
    {
	$self->rig->errorlog(" could not get respns: $cmd from controller ".$self->name." (".$self->{'_gas'}.") ! Error: ".$@,@_);
	return undef;
    }
    
    $res =~ s/\r//g;  # remove new lines from string   
    return $res;
}	


sub set_control_type()
{
    # "1" - "99"   = auto controller PI
    # "0" = PID controller 
    # "PID" alias for 0
    my $self = shift;
    my $cType = shift;
    
    if($cType eq 'PID'){ $cType = 00;}  
    
    # check $cType if between 0 and 99 and limit 
    if($cType < 0) { $cType = 0;}
    if($cType > 99) { $cType = 99;}
    
    
    my $str = "RAS=" . sprintf("%02d", $cType);		# pad with zero before 0 to 9
    
    my $res = '';
    eval {
	$res = $self->sendcmd($str);
    };
    if ($@ || ($res && $res !~ /'$str'/i))			# could maybe also validate on  "ERROR INPUT" or a "false value" if control send this response 
    {
	$self->rig->errorlog(" could not get sensor type from controller ".$self->name." (".$self->{'_gas'}.") ! Error: ".$@,@_);
	return undef;
    }
    if($cType eq 0)
    {
	$self->proglog("controller ".$self->{'_gas'}." set for PID control ",@_);
    }
    else{ $self->proglog("controller ".$self->{'_gas'}." set for AUTO(PI) control speed $cType",@_);}
    
    return $res . " please update page to refresh the gui with the new controller settings";  
    
}

sub get_settings()
{
	#retain all settings of the controller and print it and store in config file
	# get UNIT
	# get Mode
	# get Sensor 
	# get valve
	# get controller setting sd	
	my $self = shift;
	
	my $string;
	$string = $string . "Setp= " . $self->getsetpoint() ."\n" ;
    $string = $string . "Act= " . $self->read_nocache() ."\n" ;
	$string = $string .  $self->getControlTypeSetting() ."\n";
	# get PID values if  control type is '0'
		
    return $string;
	
	

#  $id->monitor_rows()   Returns a string identifying how many data rows moitor() returns
#                        and which row numbers contain data (as opposed to identifier strings)
#                        Default is the string '2' for a normal device which only returns a
#                        single value and a time. For devices returning multiple values
#                        the string returned could be '3,5' for instance (as rows 2 and 4
#                        contain the strings describing the values in row 3 and 5 respectively).
	
sub monitor()
{
	my $self = shift;
	
	my $string =  "Setp=" . $self->read_set() ;
    $string = $string . " Act=" . $self->read_nocache() ;
	if ($self->debug){
	$string = $string . " " . $self->getValue('RVO?') ;
	$string = $string . " " . $self->getValue('RVA?') ;
	$string = $string . " " . $self->getValue('VAP?') ;
	$string = $string . " " . $self->getValue('VAT?') ;
	$string = $string . " " . $self->getValue('VAS?') ;
	}
    return $string;

}
	
sub setPIDsetup($)
{
	my $self = shift;
	# run 
	$self->set_control_type(0);
	# RSP
	#RSI
	#RSD
	
	my $str = "RAS=" . sprintf("%02d", 22);		# pad with zero before 0 to 9
	
	my $res = '';
	eval {
		$res = $self->sendcmd($str);
	};
	if ($@ || $res !~ /'$str'/i)			# could maybe also validate on  "ERROR INPUT" or a "false value" if control send this response 
    {
		$self->rig->errorlog(" could not get sensor type from controller ".$self->name." (".$self->{'_gas'}.") ! Error: ".$@,@_);
		return undef;
    }
	
	
	return $res ; # 
		
	
} 	

sub setParameter
{

}

sub readonly_value($)			#  call from the guy when setup menu is opening  define setuptags for KP I and D
{	#!!!!! not sure that the Kp Tn ... is only readonly tags     will check 
    my $self = shift;
    my $tag = shift;
    return undef unless ($tag);
	return $self->getControlTypeSetting() if ($tag eq 'Controllertype');
    return $self->getKp() if ($tag eq 'Kp');
    return $self->getTn() if ($tag eq 'Tn');
	return $self->getTv() if ($tag eq 'Tv');
	# print crontrol type setting  num 0-99
    return undef;
}
sub setKp($)
{
	my $self = shift;
	my $Kp = shift;
	if($Kp < 0.1) { $Kp = 0.1;}
	if($Kp > 100) { $Kp = 100.0;}
	
	my $str = "RSP=" . sprintf("%05.1f",$Kp);
	my $res;
	eval {
		$res = $self->sendcmd($str);
    };
    if ($@ || $res =~ m/$str/)
    {
		$self->rig->errorlog("Error: Fail to set $str for ".$self->name." (".$self->{'_gas'}.") : ".$@,@_);
		return undef;
    }
	
	return $res . " please update page to refresh the gui with the new controller settings" ;
	
}
sub setTn()
{
	#0.0 and 3600.0
	#RSI=xxxx.x  ex RSI=0030.3
	my $self = shift;
	my $Tn = shift;
	if($Tn < 0.0) { $Tn = 0.0;}
	if($Tn > 3600) { $Tn = 3600.0;}
	
	my $str = "RSI=" . sprintf("%06.1f",$Tn);
	my $res;
	eval {
		$res = $self->sendcmd($str);
    };
    if ($@ || $res =~ m/$str/)
    {
		$self->rig->errorlog("Error: Fail to set $str for ".$self->name." (".$self->{'_gas'}.") : ".$@,@_);
		return undef;
    }
	
	return $res . " please update page to refresh the gui with the new controller settings" ;
}
sub setTv()
{
	#0.0 and 3600.0
	#RSD=xxxx.x  ex RSD=0021.2
	my $self = shift;
	my $Tv = shift;
	if($Tv < 0.0) { $Tv = 0.0;}
	if($Tv > 3600) { $Tv = 3600.0;}
	
	my $str = "RSD=" . sprintf("%06.1f",$Tv);
	my $res;
	print $str . "\n";
	eval {
		$res = $self->sendcmd($str);
    };
    if ($@ || $res =~ m/$str/)
    {
		$self->rig->errorlog("Error: Fail to set $str for ".$self->name." (".$self->{'_gas'}.") : ".$@,@_);
		return undef;
    }
	
	return $res . " please update page to refresh the gui with the new controller settings";
	
}

sub getKp()
{
	my $self = shift;
   	my $res = $self->getValue('RSP?');		# will also need  "FLO?"  if class should handle flow control 
	# perhaps validate if a defined eler return some number -32768 
	$res =~ s/RSP=//;	
	
	return( sprintf("%3.1f",$res));   # 
	#return $res;
}

sub getTn()
{
	my $self = shift;
   	my $res = $self->getValue('RSI?');		# will also need  "FLO?"  if class should handle flow control 
	# perhaps validate if a defined eler return some number -32768 
	$res =~ s/RSI=//;	
	
	return( sprintf("%4.1f",$res));   # 
	#return $res;
}

sub getTv()
{
	my $self = shift;
   	my $res = $self->getValue('RSD?');		# will also need  "FLO?"  if class should handle flow control 
	# perhaps validate if a defined eler return some number -32768 
	$res =~ s/RSD=//;	
	
	return( sprintf("%4.1f",$res));   # 
	#return $res;
}
	
}	

return 1;


