package RFC::Monostable;
#$Id: Monostable.pm,v 1.22 2019/08/27 07:57:36 sqko Exp $

###########################################################################
#
#
#      RFC::Monostable
#
#
#  This class defines the behaviour of monostable relays
#  To obtain a relay instance use one of the constructors like shown below:
#
#  my $id = Monostable->new($name,$rig);
#
#  my $id = PWM_Mono->new($name,$rig); 
#
#  The rig argument must be a RFC::Rig instance or a similar object honoring
#  the get_conf() member function.
#  The constructior can also be called with additional arguments:
#
#  my $id = Monostable->new($name,$rig,$device);
#
#  where the 3'rd argmument is the relay device instance to be used for the control. 
#  This device must be an instance of RFC::BaseRelay or a derived class.
#
#  The difference between the two types of monostable relays is that the PWM
#  version has varialbe on time which is determined at runtime by using the read
#  value from an other device whereas the normal one has a fixed on time 
#  (determined by the configuration).
#
#  Notice that it is possible to create a circular list of Monostable relays as 
#  RFC::Monostable is polymorphic with RFC::BaseRelay! 
#  Do not attempt this as the whole system will be inoperable due to infinite recursion!
#
#  The RFC::Monostable class derives from and emulates RFC::BaseRelay but overloads
#  the $id->set function.
#
#  $id->set($[$][$])    Triggers the the relay to shortly send a close command followed 
#                       by a wait duration and then a open command irrespective of the 
#                       first argument
#                       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! 
#
#  If no data can be read from the device attatched to the object, the read() 
#  function returns -1 to indicate the error.
#  
####################################################################



use strict;
use warnings;
use RFC::BaseRelay;
our @ISA = qw(RFC::BaseRelay);

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->{'_section'} = 'relay_'.$self->name;
    $self->{'_type'} = 'Monostable';
    $self->{'_mode'} = 'Automatic';
    $self->{'_setup_tags'} = ['mode','device','duration','title','show_plot'];
    $self->{'_reload_tags'} = ['class','mode','device'];
    $self->{'_desc'} = {
	'mode' => 'Device control mode',
	'device' => 'Relay device name used for actual control (a BaseRelay device instance)',
	'duration' => 'Duration of the on pulse in seconds',
	'title' => 'Optional title',
        'show_plot'=>'Determines if the current device data is to be shown in the daily data plots',
    };
    $self->{'_default'} = {
	'mode' => 'Automatic',
	'duration' => 1,
        'show_plot'=>'Yes',
    };
    bless $self,$class;
    foreach my $tag ('duration','title')
    {
        $self->{'_'.$tag} = $self->get_default($tag);
        my $conf = $self->get_conf($tag);
        $self->{'_'.$tag} = $conf if (defined($conf) && $conf =~ /\w/);
    }
    $self->{'_values_mode'} = ['Automatic'];
    my @list = ();
    foreach my $relay ($self->rig->avaliable_devices('relay'))
    {
	next if ($relay eq $self->name);
	push @list,$relay;
    }
    $self->{'_values_device'} = ['',@list];
    if (@_)
    {
	$self->{'_device'} = shift;
    }
    else
    {
	$self->{'_device'} = $self->rig()->get_device('relay',$self->get_conf('device'));
	unless ($self->{'_device'})
	{
	    $self->{'_device'} = $self->rig()->init_device('relay',$self->get_conf('device'));
	    $self->{'_device'}->readstring_ignore(1) if ($self->{'_device'});
	}
    }
    $self->{'_device'}->set_readonly() if ($self->{'_device'});
    return $self;
}

sub get_duration
{
    my $self = shift;
    return $self->{'_duration'};
}


sub set_noinfo($)
{
    my $self = shift;
    my $status = shift;
    my $dur = $self->get_duration();
    $self->writedebug("Triggering relay '".$self->{'_device'}->name."' for ".$dur." seconde") if ($self->debug > 2);
    $self->die("No relay device returned with name ".$self->get_conf('device')." returned") unless ($self->{'_device'});
    my $sec = int($dur);
    my $ms = $dur - $sec;
    if ($sec == 0)
    {
	$self->{'_device'}->set(1,1,1);
	$self->notify('set_noinfo',1,@_);
	select(undef,undef,undef,$ms);
	$self->{'_device'}->set(0,1,1);
	$self->notify('set_noinfo',0,@_);
    }
    else
    {
	my $end = time + $sec;
	$self->{'_device'}->set(1,1,1);
	$self->notify('set_noinfo',1,@_);
	while (time < $end)
	{
	    sleep(1);
	}
	select(undef,undef,undef,$ms);
	$self->{'_device'}->set(0,1,1);
	$self->notify('set_noinfo',0,@_);
    }
    return $status;
}

sub set($)
{
    my $self = shift;
    my $status = shift;
    my $dur = $self->get_duration();
    $self->set_noinfo($status);
    $self->proglog("Monostable relay ".$self->name." triggered for ".$dur." seconds",@_);
    return $status;
}

sub print_config()
{
    my $self = shift;
    my @arr = ('Relay type: '.$self->type);
    push @arr,'Name: '.$self->name;
    push @arr,'Duration: '.$self->get_duration();
    push @arr,'Mode: '.$self->mode;
    if ($self->{'_device'})
    {
	push @arr,'Control device: '.$self->{'_device'}->name;
	push @arr,$self->{'_device'}->print_config();
    }
    else
    {
	push @arr,'No control device specified!';
    }
    return join (', ',@arr);
}

sub read()
{
    my $self = shift;
    $self->die("No relay device returned with name ".$self->get_conf('device')." returned") unless ($self->{'_device'});
    return $self->{'_device'}->read(@_);
}

sub test()
{
    my $self = shift;
    $self->die("No relay device returned with name ".$self->get_conf('device')." returned") unless ($self->{'_device'});
    return $self->{'_device'}->test();
}

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


sub test_string()
{
    my $self = shift;
    my $retval = undef;
    eval {
	my $dur = $self->get_duration();
        $retval = 'Status for monostable relay '.$self->name.': '.$self->read(@_)."\n"."Pulse duration: $dur seconds";
    };
    if ($@)
    {
        return 'Error: could not get status for monostable relay '.$self->name.', error: '.$@;
    }
    return $retval;
}


package RFC::PWM_Mono;
our @ISA = qw(RFC::Monostable);

sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig,@_);
    $self->{'_type'} = 'Monostable-PWM';
    $self->{'_mode'} = 'Automatic';
    $self->{'_setup_tags'} = ['mode','device','timing_device_type','timing_device','title','show_plot'];
    $self->{'_reload_tags'} = ['class','mode','device','timing_device_type','timing_device'];
    $self->{'_desc'} = {
	'mode' => 'Device control mode',
	'device' => 'Relay device name used for actual control (a BaseRelay device instance)',
	'timing_device' => 'Device determining length of on state. The read value of this device uppon a recieved set command is used for the duration of the pulse. If no timing device can be loaded, the duration is fixed to 1 second. If the read value is negative, the duration is set to 0.',
	'timing_device_type' => 'Device type of the timing device',
	'title' => 'Optional title',
        'show_plot'=>'Determines if the current device data is to be shown in the daily data plots',
    };
    $self->{'_default'} = {
	'mode' => 'Automatic',
        'show_plot'=>'Yes',
	'timing_device_type' => 'simplechannel',
    };
    $self->{'_section'} = 'relay_'.$self->name;
    bless $self,$class;
    foreach my $tag ('timing_device_type','timing_device')
    {
        $self->{'_'.$tag} = $self->get_default($tag);
        my $conf = $self->get_conf($tag);
        $self->{'_'.$tag} = $conf if (defined($conf) && $conf =~ /\w/);
    }
    $self->{'_values_mode'} = ['Automatic'];
    ### Output device should have been handles by parent constructor ###
    my @list = ();
    foreach my $item ($self->rig->avaliable_devices($self->{'_timing_device_type'}))
    {
	next if ($item eq $self->name);
	push @list,$item;
    }
    $self->{'_values_timing_device'} = ['',@list];
    $self->{'_values_timing_device_type'} = [$self->rig->list_types_nofilter()];
    $self->{'_time_device'} = $self->rig->get_device($self->{'_timing_device_type'},$self->{'_timing_device'});
    unless ($self->{'_time_device'})
    {
	$self->{'_time_device'} = $self->rig->init_device($self->{'_timing_device_type'},$self->{'_timing_device'});
	$self->{'_time_device'}->readstring_ignore(1) if ($self->{'_time_device'});
    }
    return $self;
}

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


sub test()
{
    my $self = shift;
    $self->die("No relay device returned with name '".$self->get_conf('device')."' returned") unless ($self->{'_device'});
    $self->die("No device returned of type '".$self->{'_timing_device_type'}."' with name '".$self->{'_timing_device'}."' returned") unless ($self->{'_time_device'});
    return 0 unless ($self->{'_time_device'}->test());
    return $self->{'_device'}->test();
}


sub get_duration
{
    my $self = shift;
    return 1 unless ($self->{'_time_device'});
    my $val = $self->{'_time_device'}->read(@_);
    $val = 0 if ($val < 0);
    return $val;
}



return 1;
