package RFC::PLCRead;
#$Id: PLCRead.pm,v 1.19 2015/08/19 08:25:38 sqko Exp $

#################################################################
#
#
#       RFC::PLCRead
#
# This class implements (readonly) dataloging through the TCP modbus 
# RFC::PLC.pm device class 
# note PLC.pm is a singleton class that takes care of all 
# communication to and from a modbus server given by the IP adrress
# meaning all instance of PLCRead.pm with the same IP goes through
# the same PLC.pm object
#
# To obtain an instance of this class call the constructors 
# like:
#
# $id = RFC::PLCRead->new($name,$rig)
#
# 
# The RFC::PLCRead module defines no new member functions.
#
##################################################################

use strict;
use warnings;
use RFC::PLC;
use RFC::BaseDevice;
use RFC::Cache;
our @ISA = qw(RFC::BaseDevice);
use Scalar::Util qw(looks_like_number);
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'} = 'PLCRead';
    $self->{'_mode'} = 'Readonly';
    $self->{'_setup_tags'} = ['mode','IP','port','stackAddress','offset','dataType','title','show_plot'];   
    $self->{'_desc'} = {
	'mode' => 'Device read mode',
	'IP' => 'PLC IP address',
	'port' => 'modbus port number',
	'stackAddress' => ' modbus stack address index num of the variable to read',
	'offset' => ' modbus stack offset value of the variable to read',
	'dataType' => 'data type of the variable to read',
	'title' => 'Optional title',
	};
    
    $self->{'_default'} = {
        'mode' => 'Readonly',
        'IP' => '10.0.03.216',
	'port' => 502,
	'stackAddress' => 1,
	'offset' => 0,
	'dataType' => 'WORD',			# changed from WORD to black to enforce that something is selected by the user, else the defuelt is know known  
	'title' => '',
	};
    
    $self->{'_values_dataType'} = ['BOOL','BYTE','DINT','DWORD','INT','LREAL','REAL','SINT','TIME','UDINT','UINT','USINT','WORD'];
    $self->{'_values_mode'} = ['Readonly'];    
    $self->{'_section'} = 'channel_'.$self->name;
    bless $self,$class;
    foreach my $key ('IP','port','stackAddress','offset','title','show_plot') # get conf value or take default conf.
    {
        $self->{'_'.$key} = $self->get_conf($key);
        $self->{'_'.$key} = $self->get_default($key) unless ($self->{'_'.$key});
    }
    foreach my $key ('dataType') # add more 'names' for more look up tag tables
    {
	my $idx = $self->get_tag_index($key,$self->get_conf($key));
	$self->{'_'.$key} = $self->get_default($key) unless defined($idx);
	$self->{'_'.$key} = $self->{'_values_dataType'}[$idx] if defined($idx);
       # $self->{'_'.$key} = $self->get_default($key) unless (defined($self->{'_'.$key}));
    }	
    
    $self->{'_adrName'} = $self->name ;		# key name for the adr in the PLC modbus stack data structure
    
    my $device = RFC::PLC->new(IP_address => $self->{'_IP'} ); 
    # ask if name exist
    #$device->checkAdrNameExists($self->{'_adrName'})
    $device->addDataAdr($self->{'_adrName'}, $self->{'_stackAddress'}, $self->{'_offset'},$self->{'_dataType'});
    #$device->print(); 
    $self->{'_device'} =  $device ;	
    return $self; 
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Input type: '.$self->type;
    push @arr,'IP: '.$self->{'_IP'};
    push @arr,'port: '.$self->{'_port'};
    push @arr,'Stack address: '.$self->{'_stackAddress'};
    push @arr,'Stack offset: '.$self->{'_offset'};
    push @arr,'Data type: '.$self->{'_dataType'};
    return join(', ',@arr);
}


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;
} 

sub test()  # perhaps not so good a cheack but at least it check if adr is valid and there is connection to the modbus device 
{
    my $self = shift;
    my $ok = $self->{_device}->read();
    
    if($ok)
    {
	my $mrep = $self->{_device}->getSingleReading($self->{'_adrName'});
	
	if (looks_like_number($mrep) && $mrep != -32768)
	{
	    return 1;
	}
	else {return 0;}
    }
    else{
	print $self->{_device}->getLastModbusMsg() . "\n";
	return 0;
	
    }
}

sub test_string
{
    my $self = shift;
    my $res = $self->read_raw(@_);
    if ($res == -32768)
    {
	# retian error code and return fallt 
	return "modbus adr: " .$self->name . " read fall: " . $self->{_device}->getLastModbusMsg() . "\n" .  $self->{_device}->getlastModbusException() . "\n";
	
    }    
    else { return "modbus adr: ".$self->name,": $res\n";}
}

sub read()		# not finish 
{
    my $self = shift;
    my $res = $self->read_raw(@_);
    if (defined($res))
    {
	$self->writedebug("Channel ".$self->name." was read to $res") if ($self->debug > 1);
	my $str = time.' '.$self->name.' '.$res;
	$self->rig->kanaldata($str) unless ($self->readstring_ignore);
	return $res;
    }
    else
    {
	$self->writedebug("Channel ".$self->name." returned undefined on read!") if ($self->debug);
    }
    return undef;
}	
	


sub read_raw()
{
    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 $res = undef;
	# check if modbus device allready been read
	my $cached = $data->get($self->type,$self->{_device}->getIP());
	
    #    if (defined($data) && $$data && ref($$data) eq 'HASH' && exists($$$data{$self->type}) && $$$data{$self->type} && exists($$$data{$self->type}{$self->{_device}->getIP()}))
    if (defined($cached))
	{
		
	$self->writedebug("Using already measured value for ".$self->name) if ($self->debug > 3); 
	$res = $self->{_device}->getSingleReading($self->{'_adrName'}) # get data stored in modbus device object
    }
    else
    {
	# run read() and retain adr data
		
	$self->writedebug("Measuring value for ".$self->name) if ($self->debug > 3); 
	my $ok = $self->{_device}->read();
	if($ok) {$res = $self->{_device}->getSingleReading($self->{'_adrName'}); }
	else{  # try again
		select(undef, undef, undef, 0.13);		# sleep 130ms before try read again
		$ok = $self->{_device}->read();
		if($ok) {$res = $self->{_device}->getSingleReading($self->{'_adrName'}); }
		else{ 
		$res = -32768;
		# dump error msg.
		my $msg = "modbus adr: " . $self->name . " read fall msg: " . $self->{_device}->getLastModbusMsg() . "\n" .  $self->{_device}->getlastModbusException() ;
		$self->writedebug($msg); 
		warn($msg);
	
	}
	}
	#set read flag in reference hash obejct as result that the full modbus has been read
	$data->set($self->type,$self->{_device}->getIP(),1);		# 
	
    }
    unless (defined($res))
    {
	$self->writedebug("No data found for channel ".$self->name);
	return undef;
    }
    chomp $res;
    return $res;
}


sub readStored()
{
    # read object data funktion with outdoing a read of the modbus device 
    # just get stored data
}

1;
