package Eurotherm;
#$Id: Eurotherm.pm,v 1.41 2017/08/18 09:19:27 sofc Exp $
use strict;
use SocketClient;

BEGIN {
    use Exporter ();
    our ($VERSION, $COPYRIGHT, $DESIGNERS, $IMPLEMENTERS, $TESTERS, @ISA, @EXPORT, @EXPORT_OK);
    $VERSION = 1.23;
    $COPYRIGHT = "Risoe Materials Research Department";
    $DESIGNERS = 'S&oslash;ren Koch';
    $IMPLEMENTERS = 'S&oslash;ren Koch';
    $TESTERS = "";
    @ISA = qw(Exporter);
    @EXPORT = qw(new_controler);
    @EXPORT_OK = qw();
}

################################################################
###                                                         ####
### This module provides only a single public function      ####
### returning a new temperature controler object depeending ####
### on the type specified as the first argument             ####
### The second argument is the tty on which it is           ####
### controlled and the third argument is the controler      ####
### address. Any further arguments are passed directly      ####
### to the object constructor                               ####
### If any arguments are passed, they must be in the        ####
### correct order:                                          #### 
###   cmode (modbus or bisynch)                             ####
###   mode (decimal or integer)                             ####
### The default values are modbus and decimal !             ####
### (Note that for Linkam controlers cmode and mode is      ####
### ignored and can be omitted!)                            ####
###                                                         ####
################################################################

sub new_controler($@)
{
    my $type = shift;
    my $id = undef;
    $_ = $type;
  CASE:
    {
        /^E3216$/ and do {$id = e_3216->new(@_); last CASE};
        /^E2216$/ and do {$id = e_2216->new(@_); last CASE};
        /^E2208$/ and do {$id = e_2208->new(@_); last CASE};
        /^E2404$/ and do {$id = e_2404->new(@_); last CASE};
        /^E2408$/ and do {$id = e_2408->new(@_); last CASE};
        /^E2416$/ and do {$id = e_2416->new(@_); last CASE};
        /^W6100$/ and do {$id = West6100->new(@_); last CASE};
	/^Linkam/ and do {$id = Linkam->new(@_); last CASE};
    }
    return $id;
}

sub get_list()
{
    #### Returns a list of known controler types ###
    return ('E3216','E2216','E2208','E2404','E2408','E2416','W6100','Linkam');
}



############################################################################
###  Class definitions for use with Eurotherm temperature controlers    ####
###  The interface is always degrees celsius for setpoints and          ####
###  degrees celsius/hour for ramprates!                                ####
###  The internal conversions within the classes manage the differences ####
###  between the different types of controlers                          ####
###  Note that the base class Eurotherm does not do any conversion what ####
###  so ever, it merely reports the direct responses from the           ####
### controler. Thus the base class should normally nt be used directly! #### 
############################################################################


sub new
{
    my $class = shift;
    my $self = {
	'_type' => 'DEFAULT',
	'_tty' => shift,
	'_address' => shift,
	'_cmode' =>'modbus',
	'_mode' => 'decimal',
	'_ramp' => undef,
	'_pow' => undef,
	'_temp' => undef,
        '_atemp' => undef,
        '_aset' => undef
    };
    if (@_)
    {
	$self->{'_cmode'} = shift;
    }
    if (@_)
    {
	$self->{'_mode'} = shift;
    }
    bless $self, $class;
    return $self;
}

sub name()
{
    my $self = shift;
    return $self->{'_type'};
}

sub bisync_read($)
{
    my $self = shift;
    my $tag = shift;
    my $res = serial_client_raw($self->{'_tty'},'temp','r',$self->{'_address'},$tag);
    if ($res && $res =~ /$tag([\d\.\+\-]+)/)
    {
	return $1 + 0;
    }
    return undef;
}

sub bisync_send($$)
{
    my $self = shift;
    my $tag = shift;
    my $val = shift;
    serial_client_raw($self->{'_tty'},'temp','w',$self->{'_address'},$tag,$val);
    select(undef,undef,undef,1.0);
    return $self->bisync_read($tag);
}

sub modbus_int_read($)
{
    my $self = shift;
    my $tag = shift;
    my $res = serial_client_raw($self->{'_tty'},'modbus','r',$self->{'_address'},$tag);
    if ($res && $res =~ /^\s*\d+\s+\d+\s+\d+\s+(\d+)\s+(\d+)/)
    {
	return $1 * 256 + $2;
    }
}

sub modbus_int_send($$)
{
    my $self = shift;
    my $tag = shift;
    my $val = shift;
    serial_client_raw($self->{'_tty'},'modbus','w',$self->{'_address'},$tag,$val);
    select(undef,undef,undef,1.0);
    return $self->modbus_int_read($tag);
}

sub modbus_dec_read($)
{
    my $self = shift;
    my $tag = shift;
    my $res = serial_client_raw($self->{'_tty'},'modbus','g',$self->{'_address'},$tag);
    if ($res && $res =~ /([\-\d\.]+)\s*$/)
    {
	return $1;
    }
}


sub modbus_dec_send($$)
{
    my $self = shift;
    my $tag = shift;
    my $val = shift;
    serial_client_raw($self->{'_tty'},'modbus','p',$self->{'_address'},$tag,$val);
    select(undef,undef,undef,1.0);
    return $self->modbus_dec_read($tag);
}

### End utility functions ##

sub get_power()
#########################################################
###                                                  ####
###  Returns the power output in % of full scale     ####
###                                                  ####
#########################################################
{
    my $self = shift;
    $self->{'_pow'} = undef;
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    $self->{'_pow'} = $self->modbus_dec_read(3);
	}
	else
	{
	    $self->{'_pow'} = $self->modbus_int_read(3);
	}
    }
    else
    {
	$self->{'_pow'} = $self->bisync_read('OP');
    }
    return $self->{'_pow'};
}

sub get_temp()
#########################################################
###                                                  ####
###  Returns the temperature setpoint                ####
###                                                  ####
#########################################################
{
    my $self = shift;
    $self->{'_temp'} = undef;
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    $self->{'_temp'} = $self->modbus_dec_read(2);
	}
	else
	{
	    $self->{'_temp'} = $self->modbus_int_read(2);
	}
    }
    else
    {
	$self->{'_temp'} = $self->bisync_read('SL');
    }
    return $self->{'_temp'};
}

sub get_ramp()
#########################################################
###                                                  ####
###  Returns the ramp rate in C/hour                 ####
###                                                  ####
#########################################################
{
    my $self = shift;
    $self->{'_ramp'} = undef;
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    $self->{'_ramp'} = $self->modbus_dec_read(35);
	}
	else
	{
	    $self->{'_ramp'} = $self->modbus_int_read(35);
	}
    }
    else
    {
	$self->{'_ramp'} = $self->bisync_read('RR');
    }
    return $self->{'_ramp'};
}

sub get_active_setpoint()
#########################################################
###                                                  ####
###  Returns the local setpoint (not target)         ####
###                                                  ####
#########################################################
{
    my $self = shift;
    $self->{'_aset'} = undef;
    if ($self->{'_cmode'} eq 'modbus')
    {
        if ($self->{'_mode'} eq 'decimal')
        {
	    $self->{'_aset'} = $self->modbus_dec_read(5);
        }
        else
        {
	    $self->{'_aset'} = $self->modbus_int_read(5);
        }
    }
    else
    {
	$self->{'_aset'} = $self->bisync_read('SP');
    }
    return $self->{'_aset'};
}

sub get_active_temp()
#########################################################
###                                                  ####
###  Returns the current temperature                 ####
###                                                  ####
#########################################################
{
    my $self = shift;
    $self->{'_atemp'} = undef;
    if ($self->{'_cmode'} eq 'modbus')
    {
        if ($self->{'_mode'} eq 'decimal')
        {
	    $self->{'_atemp'} = $self->modbus_dec_read(1);
        }
        else
        {
	    $self->{'_atemp'} = $self->modbus_int_read(1);
        }
    }
    else
    {
           $self->{'_atemp'} = $self->bisync_read('PV');
    }
    return $self->{'_atemp'};
}



sub set_temp($)
#########################################################
###                                                  ####
###  Sets the temperature setpoint (target)          ####
###                                                  ####
#########################################################
{
    my $self = shift;
    $self->{'_temp'} = shift;
    return $self->send_temp();
}

sub send_temp($)
{
    my $self = shift;
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    my $t = $self->modbus_dec_send(2,$self->{'_temp'});
	    unless ($self->temp_ok($t))
	    {
		$t = $self->modbus_dec_send(2,$self->{'_temp'});
		unless ($self->temp_ok($t))
		{
		    die "Error communicating with controler at address $self->{'_address'} on tty $self->{'_tty'} using decimal modbus. Recieved: '$t', expected '".$self->{'_temp'}."'\n";
		    return undef;
		}
	    }
	}
	else
	{
	    my $t = $self->modbus_int_send(2,$self->{'_temp'});
	    unless ($self->temp_ok($t))
	    {
		my $t = $self->modbus_int_send(2,$self->{'_temp'});
		unless ($self->temp_ok($t))
		{
		    die "Error communicating with controler at address $self->{'_address'} on tty $self->{'_tty'} using integer modbus. Recieved: '$t', expected '".$self->{'_temp'}."'\n";
		    return undef;
		}
	    }
	}
    }
    else
    {
	my $t = $self->bisync_send('SL',$self->{'_temp'});
	unless ($self->temp_ok($t))
	{
	    my $t = $self->bisync_send('SL',$self->{'_temp'});
	    unless ($self->temp_ok($t))
	    {
		die "Error communicating with controler at address $self->{'_address'} on tty $self->{'_tty'} using bisynch. Recieved '$t', expected '".$self->{'_temp'}."'\n";
		return undef;
	    }
	}
    }
    return $self->{'_temp'};
}

sub set_ramp($)
#########################################################
###                                                  ####
###  Sets the ramp rate in C/hour                    ####
###                                                  ####
#########################################################
{
    my $self = shift;
    $self->{'_ramp'} = shift;
    $self->{'_ramp'} = 1 if ($self->{'_ramp'} < 1);
    return $self->send_ramp();
}

sub temp_ok($)
{
    ### Returns 1 if the supplied setpoint is less than 0.01 C off the expected value ###
    my $self = shift;
    my $allowed_error = 0.01;
    my $temp = shift;
    my $diff = abs($temp - $self->{'_temp'});
    return 1 if ($diff < $allowed_error);
    return 0;
}


sub ramp_ok($)
{
    ### Returns 1 if the supplied ramprate is less than 1 % off the expected value ###
    my $self = shift;
    my $allowed_error = 0.01;
    my $ramp = shift;
    my $diff = abs($ramp - $self->{'_ramp'});
    return 1 if ($diff/$self->{'_ramp'} < $allowed_error);
    return 0;
}

sub send_ramp($)
{
    my $self = shift;
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    my $t = $self->modbus_dec_send(35,$self->{'_ramp'});
	    unless ($self->ramp_ok($t))
	    {
		my $t = $self->modbus_dec_send(35,$self->{'_ramp'});
		unless ($self->ramp_ok($t))
		{
		    die "Error communicating with controler at address $self->{'_address'} on tty $self->{'_tty'} using decimal modbus. Recieved: '$t', expected '".$self->{'_ramp'}."'\n";
		    return undef;
		}
	    }
	}
	else
	{
	    $self->{'_ramp'} = int($self->{'_ramp'} + 0.5);  ## Round ramprate to nearest integer ###
	    my $t = $self->modbus_int_send(35,$self->{'_ramp'});
	    unless ($t == $self->{'_ramp'})
	    {
		my $t = $self->modbus_int_send(35,$self->{'_ramp'});
		unless ($self->{'_ramp'} == $t)
		{
		    die "Error communicating with controler at address $self->{'_address'} on tty $self->{'_tty'} using integer modbus. Recieved: '$t', expected '".$self->{'_ramp'}."'\n";
		    return undef;
		}
	    }
	}
    }
    else
    {
	my $t = $self->bisync_send('RR',$self->{'_ramp'});
	unless ($t == $self->{'_ramp'})
	{
	    my $t = $self->bisync_send('RR',$self->{'_ramp'});
	    unless ($t == $self->{'_ramp'})
	    {
		die "Error communicating with controler at address $self->{'_address'} on tty $self->{'_tty'} using bisynch. Recieved '$t', expected '".$self->{'_ramp'}."'\n";
		return undef;
	    }
	}
    }
    return $self->{'_ramp'};
}

######################
##                  ##
##  End base class  ##
##                  ##
######################

package e_2216;

our @ISA = qw( Eurotherm );

sub new
{
    my $type = shift;
    my $self = $type->SUPER::new(@_);
    bless $self, $type;
    $self->{'_type'} = '2216e';
    return $self;
}

sub set_ramp($)
{
    my $self = shift;
    my $r = shift;
    $self->{'_ramp'} = $r;
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    $self->{'_ramp'} = $r / 60;
	    $self->{'_ramp'} = 0.1 if ($self->{'_ramp'} < 0.1);
	}
	else
	{
	    $self->{'_ramp'} = $r / 6; 
	    $self->{'_ramp'} = 1 if ($self->{'_ramp'} < 1);
	}
    }
    else
    {
	$self->{'_ramp'} = $r;
	$self->{'_ramp'} = 1 if ($self->{'_ramp'} < 1);
    }
    return $self->send_ramp();
}

sub get_ramp()
{
    my $self = shift;
    my $ramp = $self->SUPER::get_ramp();
    return undef unless defined($ramp);
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    $self->{'_ramp'} = $ramp * 60;
	}
	else
	{
	    $self->{'_ramp'} = $ramp * 6;
	}
    }
    else
    {
	$self->{'_ramp'} = $ramp;
    }
    return $self->{'_ramp'};
}

######################

package e_3216;

our @ISA = qw( e_2216 );

sub new
{
    my $type = shift;
    my $self = $type->SUPER::new(@_);
    bless $self, $type;
    $self->{'_type'} = '3216e';
    return $self;
}


######################

package e_2208;

our @ISA = qw( Eurotherm );

sub new
{
    my $type = shift;
    my $self = $type->SUPER::new(@_);
    bless $self, $type;
    $self->{'_type'} = '2208e';
    return $self;
}

sub set_ramp()
{
    my $self = shift;
    my $r = shift;
    $self->{'_ramp'} = $r; 
    $self->{'_ramp'} = 6 if ($self->{'_ramp'} < 6);
    return $self->send_ramp();
}


######################

package e_2404;

our @ISA = qw( Eurotherm );

sub new
{
    my $type = shift;
    my $self = $type->SUPER::new(@_);
    bless $self, $type;
    $self->{'_type'} = '2404e';
    return $self;
}

sub set_ramp($)
{
    my $self = shift;
    my $r = shift;
    $self->{'_ramp'} = $r;
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    $self->{'_ramp'} = $r / 3600;
	    $self->{'_ramp'} = 0.001 if ($self->{'_ramp'} < 0.001);
	}
	else
	{
	    $self->{'_ramp'} = undef; 
	    die "Controler 2404 can not be controlled in integer mode!";
	}
    }
    else
    {
	$self->{'_ramp'} = $r;
	$self->{'_ramp'} = 1 if ($self->{'_ramp'} < 1);
    }
    return $self->send_ramp();
}

sub get_ramp()
{
    my $self = shift;
    my $ramp = $self->SUPER::get_ramp();
    return undef unless defined($ramp);
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    $self->{'_ramp'} = $ramp * 3600;
	}
	else
	{
	    $self->{'_ramp'} = undef;
	}
    }
    else
    {
	$self->{'_ramp'} = $ramp;
    }
    return $self->{'_ramp'};
}

######################

package e_2408;

our @ISA = qw( Eurotherm );

sub new
{
    my $type = shift;
    my $self = $type->SUPER::new(@_);
    bless $self, $type;
    $self->{'_type'} = '2408e';
    return $self;
}

sub set_ramp($)
{
    my $self = shift;
    my $r = shift;
    $self->{'_ramp'} = $r;
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    $self->{'_ramp'} = $r / 3600;
	    $self->{'_ramp'} = 0.001 if ($self->{'_ramp'} < 0.001);
	}
	else
	{
	    $self->{'_ramp'} = undef; 
	    die "Controler 2408 can not be controlled in integer mode!";
	}
    }
    return $self->send_ramp();
}

sub get_ramp()
{
    my $self = shift;
    my $ramp = $self->SUPER::get_ramp();
    return undef unless defined($ramp);
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    $self->{'_ramp'} = $ramp * 3600;
	}
	else
	{
	    $self->{'_ramp'} = undef;
	}
    }
    else
    {
	$self->{'_ramp'} = $ramp;
    }
    return $self->{'_ramp'};
}

######################

package e_2416;

our @ISA = qw( Eurotherm );

sub new
{
    my $type = shift;
    my $self = $type->SUPER::new(@_);
    bless $self, $type;
    $self->{'_type'} = '2416e';
    return $self;
}

sub set_ramp($)
{
    my $self = shift;
    my $r = shift;
    $self->{'_ramp'} = $r;
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_mode'} eq 'decimal')
	{
	    $self->{'_ramp'} = $r;
	    $self->{'_ramp'} = 0.001 if ($self->{'_ramp'} < 0.001);
	}
	else
	{
	    $self->{'_ramp'} = $r; 
	    $self->{'_ramp'} = 1 if ($self->{'_ramp'} < 1);
	}
    }
    else
    {
	$self->{'_ramp'} = $r;
	$self->{'_ramp'} = 1 if ($self->{'_ramp'} < 1);
    }
    return $self->send_ramp();
}

sub get_ramp()
{
    my $self = shift;
    my $ramp = $self->SUPER::get_ramp();
    return undef unless defined($ramp);
    if ($self->{'_cmode'} eq 'modbus')
    {
	if ($self->{'_cmode'} eq 'modbus')
	{
	    $self->{'_ramp'} = $ramp;
	}
	else
	{
	    $self->{'_ramp'} = $ramp;
	}
    }
    else
    {
	$self->{'_ramp'} = $ramp;
    }
    return $self->{'_ramp'};
}

########################

package West6100;
use SocketClient;
our @ISA = qw( Eurotherm );

sub new
{
    my $type = shift;
    my $self = $type->SUPER::new(@_);
    bless $self, $type;
    $self->{'_type'} = 'West6100';
    $self->{'_cmode'} = 'modbus';
    $self->{'_mode'} = 'integer';
    return $self;
}

sub get_power()
{
    my $self = shift;
    my $pow = $self->modbus_int_read(3);
    return $pow;
}

sub set_temp($)
{
    my $self = shift;
    $self->{'_temp'} = shift;
    return $self->modbus_int_send(2,$self->{'_temp'} * 10 ) / 10;
}

sub get_temp()
{
    my $self = shift;
    $self->{'_temp'} = $self->modbus_int_read(2) / 10;
    return $self->{'_temp'};
}

sub set_ramp($)
{
    my $self = shift;
    $self->{'_ramp'} = shift;
    $self->{'_ramp'} = 1 unless ($self->{'_ramp'} && $self->{'_ramp'} > 1);
    return $self->modbus_int_send(24,$self->{'_ramp'} * 10) / 10;
}

sub get_ramp()
{
    my $self = shift;
    $self->{'_ramp'} = $self->modbus_int_read(24) / 10;
    return $self->{'_ramp'};
}

sub get_active_setpoint()
{
    my $self = shift;
    $self->{'_aset'} = $self->modbus_int_read(21) / 10;
    return $self->{'_asset'};
}

sub get_active_temp()
{
    my $self = shift;
    $self->{'_atemp'} = $self->modbus_int_read(1) / 10;
    return $self->{'_atemp'};
}


package Linkam;
use SocketClient;

####################################################
##                                                ##
## Note this class overrides all base functions!  ##
##                                                ##
####################################################

our @ISA = qw( Eurotherm );

sub new
{
    my $type = shift;
    my $self = $type->SUPER::new(@_);
    $self->{'_file'} = '/var/Linkam_'.$self->{'_tty'}.'_'.$self->{'_address'}.'.conf';
    bless $self, $type;
    $self->{'_type'} = 'Linkam';
    return $self;
}

sub get_active_temp()
{
    my $self = shift;
    my $temp = serial_client_raw($self->{'_tty'},'linkam','get');
    chomp $temp;
    $self->{'_atemp'} = $temp;
    return $self->{'_atemp'};
}

sub get_active_setpoint()
{
    my $self = shift;
    $self->{'_aset'} = undef;
    return $self->{'_aset'};
}

sub get_power()
{
    my $self = shift;
    return -32768;
}

sub set_temp()
{
    my $self = shift;
    $self->{'_temp'} = shift;
    $self->go_linkam();
    return $self->{'_temp'};
}

sub set_ramp()
{
    my $self = shift;
    $self->{'_ramp'} = shift;
    $self->go_linkam();
    return $self->{'_ramp'};
}

sub send_temp()
{
    my $self = shift;
    $self->go_linkam();
}

sub send_ramp()
{
    my $self = shift;
    $self->go_linkam();
}

sub ramp_ok()
{
    my $self = shift;
    return 1;   ## Assumes always ok as there is no way to query the Linkam controler for ramprate.... ###
}

sub go_linkam()
{
    my $self = shift;
    my $delay = 0.2;
    ### Get ramparte from file if not specified (as the Linkam does not work with empty ramprate) ###
    unless (defined $self->{'_ramp'})
    {
	if (-e $self->{_file})
	{
	    open LIN,'<',$self->{'_file'} or die "Could not open file $self->{'_file'} for reading! $!";
	    $self->{'_ramp'} = <LIN>;
	    close LIN;
	    chomp $self->{'_ramp'};
	}
    }
    ### Get temp from file if not specified (as the Linkam does not work with empty temp) ###
    unless (defined $self->{'_temp'})
    {
	if (-e $self->{'_file'})
	{
	    open LIN,'<',$self->{'_file'} or die "Could not open file $self->{'_file'} for reading! $!";
	    my $dummy = <LIN>;
	    $self->{'_temp'} = <LIN>;
	    close LIN;
	    chomp $self->{'_temp'};
	}	
    }
    serial_client_raw($self->{'_tty'},'linkam','raw','E');
    select(undef,undef,undef,$delay);
    ### Rampate is set in degrees/minute! ###
    serial_client_raw($self->{'_tty'},'linkam','ramp',$self->{'_ramp'}/60);
    select(undef,undef,undef,$delay);
    serial_client_raw($self->{'_tty'},'linkam','temp',$self->{'_temp'});
    select(undef,undef,undef,$delay);
    serial_client_raw($self->{'_tty'},'linkam','raw','S');
    select(undef,undef,undef,$delay);
    open LIN,'>',$self->{'_file'} or die "Could not open file $self->{'_file'} for writing! $!";
    print LIN $self->{'_ramp'},"\n";
    print LIN $self->{'_temp'},"\n";
    close LIN;
}

sub get_temp()
{
    my $self = shift;
    ### Get temp from file there is no way to query the Linkam controler for temperature setpoint.... ###
    if (-e $self->{'_file'})
    {
	open LIN,'<',$self->{'_file'} or die "Could not open file $self->{'_file'} for reading! $!";
	my $dummy = <LIN>;
	$self->{'_temp'} = <LIN>;
	close LIN;
	chomp $self->{'_temp'};
    }	
    return $self->{'_temp'};
}

sub get_ramp()
{
    my $self = shift;
    ### Get ramparte from file as there is no way to query the Linkam controler for ramprate.... ###
    if (-e $self->{'_file'})
    {
	open LIN,'<',$self->{'_file'} or die "Could not open file $self->{'_file'} for reading! $!";
	$self->{'_ramp'} = <LIN>;
	close LIN;
	chomp $self->{'_ramp'};
    }
    return $self->{'_ramp'};
}

return 1;

