package RFC::Julabo;
#$Id: Julabo.pm,v 1.4 2022/02/25 12:06:49 sqko Exp $
use strict;
use warnings;
use SocketClient;
use RFC::Main qw();
use RFC::Header;
use RFC::BaseDevice;
use RFC::Cache;
our @ISA = qw(RFC::BaseDevice);
###############################################################################
#
#
#   Package RFC::Julabo
#
#
# This class defines the behaviour of Julabo thermal heat bath control. 
# To obtain aTempControl instance call one of the constructors
# like shown below:
#
# $id = RFC::TempControl->new($name,$rig)
#
# Julabo objects have the following member functions in addition to those
# derived from RFC::BaseDevce and Debug.pm:
#
#
# $id->readstring()	 returns string with SetP Temp, active Temp, Output Power 
# $id->get_atemp()       returns the active (measured) temperature.
# $id->get_output()      returns the output power.
#
# $id->get_offset()      Returns the offset between the temperature setpoint
#                        and the actual measured temperature.
#                        For a functiioning controler this should be close to
#                        0 but if the furnace can not heat suficiently (due to broken
#                        heating coils or similar) it will be positive.
# $id->set_temp($[$][$]) Sets the temperature setpoint.
#                        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 set_temp.
#
#							""private member functions""
# $id->get_transaction($cmd)		send read's commands to the Julabo controller and return the answer 
# $id->get_transaction($cmd, $arg) 	send set's commands to the julabo controller and return the answer
#
###############################################################################




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'} = 'Julabo';
    $self->{'_mode'} = 'Automatic';
    $self->{'_monitor_rows'} = '3,5,7';
    $self->{'_info_string'} = 'set 0 to stop circulator';
    $self->{'_setup_tags'} = ['mode','controler_type','tty','baudrate','parity','handshake','show_plot','use_cache'];
    $self->{'_desc'} = {
	'mode' => 'Device control mode',
	'controler_type' => 'Device type',
	'tty' => 'Communication device',
	'baudrate' => 'baudrate',
	'parity' => 'parity',
	'handshake' => 'handshake type',
	'title' => 'Optional device title',
	'use_cache'=>'Determines if persisten caching of read values is allowed',
    };
    $self->{'_default'} = {
	'mode' => 'Automatic',
	'controler_type' => 'Julabo',
	'tty' => 'ttyS0',
	'baudrate' => 4800,
	'parity' => 'even',
	'handshake' => 'rts',
    'show_plot'=>'Yes',
    'use_cache'=>'No',
    };
    $self->{'_readstring_furnace'} = 1;
    $self->{'_has_ramp'} = 0;
    #$self->{'_set_fn_names'} = ['set_tenp','set_ramp']; # List of function names which can trigger a notify event
    $self->{'_values_mode'} = ['Manual','Automatic','Readonly'];
    $self->{'_values_controler_type'} = [(RFC::Device::list_TEMP())];
    $self->{'_values_baudrate'} = [1200,2400,4800,9600];
    $self->{'_values_parity'} = ['even','none','odd'];
    $self->{'_values_handshake'} = ['rts','xoff','none'];
    $self->{'_read_names'} = ['read','get_atemp','get_output','get_temp','get_offset'];
    $self->{'_section'} = 'TControl_'.$self->name;
    bless $self,$class;
  
    foreach my $tag ('tty','title','show_plot')
    {
	$self->{'_'.$tag} = $self->get_conf($tag);
        $self->{'_'.$tag} = $self->get_default($tag) unless (defined($self->{'_'.$tag}));
    }
    
    foreach my $tag ('baudrate','parity','handshake') # add more 'names' for more look up tag tables
    {
	my $idx = $self->get_tag_index($tag,$self->get_conf($tag));
	$self->{'_'.$tag} = $self->get_default($tag) unless defined($idx);
	$self->{'_'.$tag} = $self->{'_values_' . $tag }[$idx] if defined($idx);
      # $self->{'_'.$tag} = $self->get_default($tag) unless (defined($self->{'_'.$tag}));
    }	
    # not sure these needed 
    $self->{'_communication_baudrate'} = $self->{'_baudrate'};
    $self->{'_communication_parity'} = $self->{'_parity'};
    $self->{'_communication_handshake'} = $self->{'_handshake'};
    $self->{'_readonly'} = 1 if ($self->mode =~ /readonly/i);
    return $self;
};

sub get_offset
{
    my $self = shift;
    my $sp = $self->read(@_);
    my $at = $self->get_atemp(@_);
    my $diff = $sp - $at;
    return $diff;
}


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(@_);
    #my $val = $self->read($data,@_);
    $val = -32768 unless (defined($val) && $val =~ /\d/);
    my $aset = $self->get_atemp($data,@_);
    $aset = -32768 unless (defined($aset) && $aset =~ /\d/);
    my $pow = $self->get_output($data);
    $pow = -32768 unless (defined($pow) && $pow =~ /\d/);
    my @t = $self->get_time();
    my $t = join(':',@t);
    my $res = join(' ',$self->name.'_setpoint',$t,time,$val,$self->name.'_temp',$t,time,$aset,$self->name.'_output',$t,time,$pow);
    $self->writedebug($res) if ($self->debug > 1);
    return $res;
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Type: '.$self->type;
    push @arr,'Mode: '.$self->mode;
    push @arr,'Tty: '.$self->{'_tty'};
    push @arr,'Communication Baudrate: '.$self->{'_communication_baudrate'};
    push @arr,'Communication Parity: '.$self->{'_communication_parity'};
    push @arr,'Communication handshake: '.$self->{'_communication_handshake'};
    return join(', ',@arr);    
}

sub read_nocache
{
    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 $cached = $data->get($self->{'_tty'},'temp');
    return $cached if (defined($cached));
    my $temp = undef;
    eval {
	$temp =  $self->get_transaction(@_,'in_sp_00');
	unless (defined($temp))
	{
	    select(undef,undef,undef,0.5);
	    $temp =  $self->get_transaction(@_,'in_sp_00') 
	}
	$temp = $self->get_transaction(@_,'in_pv_00') unless (defined($temp) && $temp ne '');
	unless (defined($temp))
	{
	    select(undef,undef,undef,0.5);
	    $temp =  $self->get_transaction(@_,'in_pv_00') 
	}
	die "Could not get temperature for $self\n" unless (defined($temp) && $temp ne '');
    };
    if ($@)
    {
	my $message = "Error: $@";
	$self->rig->warning_mail($self,$message,'Error for '.$self->rig);
    }
    $temp = -32768 unless defined($temp);
    $data->set($self->{'_tty'},'temp',$temp);
    $self->writedebug("Undefiend temperature returned by controler device $self") if ($self->debug && (!defined($temp) || $temp !~ /\d+/));
    $self->set_cache($temp);
    $self->{'_last_read'} = Time::HiRes::time;
    return $temp;
}

sub get_atemp
{
    my $self = shift;
    if ($self->{'_use_cache'} && $self->{'_use_cache'} eq 'Yes')
    {
        my $val = $self->rig->get_cache($self->type,$self->name.'_atemp'); 
        return $val if (defined($val));
    }
    my $data = undef;
    if (@_)
    {
        $data = shift;
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    $self->check_cache($data);
    my $cached = $data->get($self->{'_tty'},'atemp');
    return $cached if (defined($cached));
    my $temp = undef;
    eval {
	$temp =  $self->get_transaction('in_pv_00');
	
	unless (defined($temp) && $temp =~ /\d+/)
	{
	    select(undef,undef,undef,0.5);
	    $temp =  $self->get_transaction('in_pv_00');
	}
	die "Undefined active temperature returned by device ".ref($self). "\n" unless (defined($temp) && $temp =~ /\d+/);
    };
    if ($@)
    {
	my $message = "Warning: $@";
	$self->rig->warning_mail($self,$message,'Warning for '.$self->rig);
	$temp = undef;
    }
    $data->set($self->{'_tty'},'atemp',$temp);
    $self->writedebug("Undefiend active temperature returned by controler device $self") if ($self->debug && (!defined($temp) || $temp !~ /\d+/));
    $self->rig->set_cache($self->type,$self->name.'_atemp',$temp);
    return $temp;
}

sub get_output
{
    my $self = shift;
    if ($self->{'_use_cache'} && $self->{'_use_cache'} eq 'Yes')
    {
        my $val = $self->rig->get_cache($self->type,$self->name.'output'); 
        return $val if (defined($val));
    }
    my $data = undef;
    if (@_)
    {
        $data = shift;
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    $self->check_cache($data);
    my $cached = $data->get($self->{'_tty'},'output');
    return $cached if (defined($cached));
    my $temp = undef;
    eval {
	$temp = $self->get_transaction('in_pv_01'); 
	unless (defined($temp))
	{
	    select(undef,undef,undef,0.5);
	    $temp = $self->get_transaction('in_pv_01');  
	}
	die "Undefined output power returned by device ".ref($self). "\n" unless (defined($temp) && $temp ne '');
    };
    if ($@)
    {
	my $message = "Warning: $@";
	$self->rig->warning_mail($self,$message,'Warning for '.$self->rig);
	$temp = undef;
    }
    $data->set($self->{'_tty'},'output',$temp);
    $self->writedebug("Undefiend output power returned by controler device $self") if ($self->debug && (!defined($temp) || $temp !~ /\d+/));
    $self->rig->set_cache($self->type,$self->name.'output',$temp);
    return $temp;
}


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);		# not sure what this line do any good for
    my ($val,$aset,$pow) = (-32768,-32768,-32768);
    $val = $self->read();
    eval{
	$aset =  $self->get_transaction('in_pv_00');
	unless(defined($aset))
	{
	    select(undef,undef,undef,0.5);
	    $aset =  $self->get_transaction('in_pv_00');
	}
	die "Undefined active temperature returned by device ".ref($self)."\n" unless (defined($aset) && $aset ne '');
	$pow = $self->get_output();
	die "Undefined output power returned by device ".ref($self)."\n" unless (defined($pow) && $pow ne '');
    };
    if ($@)
    {
	$self->writedebug("Warning for $self: $@");
	my $message = "Warning: $@";
	$self->rig->warning_mail($self,$message,'Warning for '.$self->rig);
    }
    foreach ($val,$aset,$pow)
    {
	$_ = -32768 unless (defined($_) && /\d/);
    }
	
    
    my $status_msg = $self->get_transaction('status');
    
    return join(' ','Setpoint',$val,'Active_temp',$aset,'Output',$pow, 'status_code:', $status_msg);
}

sub get_temp
{
    my $self = shift;
    my $res = undef;
    eval {
	$res = $self->get_transaction('in_sp_00');
	unless (defined($res))
	{
	    select(undef,undef,undef,0.5);
	    $res = $self->get_transaction('in_sp_00');
	}
	die "Undefined setpoint returned by device ".ref($self)."\n" unless (defined($res) && $res ne '');
    };
    if ($@)
    {
	$self->writedebug("Warning for $self: $@");
	my $message = "Warning: $self - $@";
	$self->rig->warning_mail($self,$message,'Warning for '.$self->rig);
    }
    return $res;
}


sub set_temp()
{
    my $self = shift;
    my $temp = shift;
    my $ignore = $self->rig->disable_warning_mails();
    $self->rig->disable_warning_mails(0);
    if ($self->mode =~ /auto|read/i)
    {
	my $res = undef;
	eval {
	    # ! perhaps check if $temp is a number of correct format 
	    ## also remeber to start flow if other value than zero  and stop if zero
	    if($temp != 0) {
		$res = $self->set_transaction('out_sp_00',$temp);
		if(!($res eq '02 REMOTE STOP' or $res eq '03 REMOTE START')) # throw exception 
		{  
		    $self->writedebug("Error setting temperature for $self: $@ status msg: $res");
		}
	    }
	    $res =  $self->set_transaction('out_mode_01',0);	# always use working temperature "T1" 
	    
	    if($temp ne 0) {
		
		$res =  $self->set_transaction('out_mode_05',1);	# start circulator 
		
	    }
	    else{
		
		$res =  $self->set_transaction('out_mode_05', 0);
		
	    }	# stop circulator
	    
	    if(($temp != 0) and ($res ne '03 REMOTE START'))
	    {
		$self->writedebug("Error fail to start circulator $self: $@ status msg: $res");
	    }
	    elsif(($temp == 0) and ($res ne '02 REMOTE STOP'))
	    {
		$self->writedebug("Error fail to stop circulator $self: $@ status msg: $res");
	    }
	};
	if ($@)
	{
	    $self->writedebug("Error setting temperature for $self: $@");
	    my $message = "Error setting temperature for $self: $@";
	    $self->rig->warning_mail($self,$message,'Error for '.$self->rig);
	    $self->rig->disable_warning_mails($ignore);
	    return undef;
	}	
	$self->proglog("Temperature for $self set to $temp C",@_);
    }
    else
    {
	$self->proglog("Temperature for $self set manually to $temp C",@_);
    }
    $self->notify('set_temp',$temp,@_);
    $self->rig->unset_warning($self);
    $self->rig->disable_warning_mails($ignore);
    return $temp;
}

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

sub test()
{
    my $self = shift;
    my $res =  $self->get_temp();
    return 1 if (defined($res) && $res =~ /\d/ && ($res !~ /refused/i));
    return 0;
}

sub test_string()
{
    my $self = shift;
    my $temp = $self->get_temp();
    chomp $temp;
    
  
    return "Temperature controler $self, Setpoint: $temp C";
}

# the following function are meant as local functions 
sub set_transaction
{
    my $self = shift;
    my $cmd = shift;
    my $arg = shift;
    my $rec = undef;
    $rec = SocketClient::serial_client_raw($self->{'_tty'}, 'julabo_raw', $self->{'_baudrate'},$self->{'_parity'},$self->{'_handshake'},$cmd , $arg);
    chomp($rec); 
    return $rec;
}

sub get_transaction
{
    my $self = shift;
    my $cmd = shift;
    my $rec = undef;
    $rec = SocketClient::serial_client_raw($self->{'_tty'}, 'julabo_raw', $self->{'_baudrate'},$self->{'_parity'},$self->{'_handshake'},$cmd);
    chomp($rec) ;  
    return $rec;
}

sub get_tag_index()
{
    my $self = shift;
    my $tag = shift;
    my $value = shift;
    return undef unless defined($value);
    if (exists($self->{'_values_'.$tag}))
    {
	my @l = @{$self->{'_values_'.$tag}};
	for (my $x = 0; $x < @l; $x++)
	{
	    return $x if ($l[$x] eq $value)
	}
    }
    return undef;
} 



return 1;
