package RFC::Multiplex;
#$Id: Multiplex.pm,v 1.112 2023/11/25 07:03:51 sqko Exp $

###################################################################
#
#
#     RFC::Multiplex
# 
#  This class defines the control of multiple gasses to the same
#  MFC. To obtain a Multiplexer instance call the constructor:
#
#  $id = RFC::Multiplex->new($name,$rig)
#
#  $id = RFC::Multiplex_slave->new($name,$rig)
#
#  $id = RFC::VICI->new($name,$rig)
#
#  $id = RFC::Selector->new($name,$rig)
#
#
#  The slave version is intended to be used in case a single multiplexer
#  feeds more than one control device (MFC) and as a multiplexer 
#  device can only handle a single control device, a slave multiplexer
#  can be used on all but the first.
#  the slave multiplexers should then reference the sigle normal multiplexer
#  and will mirror the settings of the master device.
#
#  The constructor can also be called with an additional argument
#  which must then be a RFC::MFC (or derived) device instance:
#
#  $mfc = RFC::MFC->new($name,$rig);
#
#  $id = RFC::Multiplex->new($name,$rig,$mfc);
#
#  
#  All Multiplex objects have the following member functions (excluding 
#  those derived from RFC::BaseDevce and Debug.pm):
#
#  $id->attatch_controler($)   Attatches an RFC::MFC instance to the
#                              multiplexer (if not included in the
#                              call of the constructor).
#  $id->controler()            Returns the attatched MFC device.
#  $id->relays()               Returns the list of relay devices
#                              used for controling the gasses.
#  $id->set($)                 Sets the multiplexer to select the
#                              specified gas name.
#  $id->gasses()               Returns a list of possible gasses.
#  $id->label()                Returns the label string (if any)
#                              Labels can be used as identifiers instead of device name
#                              as multiplexers for historic reasons can only have 
#                              integers as name.
#                              Returns the device name if no label is set.
#  $id->gas()                  Returns the name of the selected gas (NB NOT device name).
#                              If supplied with an optional argument this is used
#                              as a data structure to access if the device had
#                              already been read (similar to the read() and readstring())
#                              member functions inherited rom BaseDevice.
#  $id->gas_name()             Returns the device name of the selected gas.
#                              If supplied with an optional argument this is used
#                              as a data structure to access if the device had
#                              already been read (similar to the read() and readstring())
#                              member functions inherited rom BaseDevice.
#
# Prior to version 4.2.5, the implementation of the RFC::Multiplex class assumed 
# that all gasses were controled from the same relay board (usually a ICP-con relay module)
# Thus all gas relays shared the same device tty and address, but differed
# only in the channel number. As of version 5.0 this is deprecated and will only
# be avaliable for configuration if legacy mode is enabled (it will still work for
# already configured systems).
#
# As of version 4.2.5 and onwards, it is possible to use arbitrary relay
# devices to control a gas multiplexer. To do so, simply configure the relays
# as normal RFC::Relay instances and select the proper names (defined in the
# '$gasname'_device_name key in the configuration section ($gasname being the name of
# the gas line in question).
#
# The Selector multiplex class is a virtual device and does not control any gas but can be
# used to select integer values (for use in other devices such as the input multiplex filter
# device.
#
###################################################################

use strict;
use warnings;
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 $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_html_template'} = 'Multiplex.tt';
    $self->{'_controler'} = undef;
    $self->{'_controler'} = shift if (@_);
    $self->{'_setup_tags'} = ['type','mode','title','show_plot','use_cache','label'];
    $self->{'_legacy_tags'} = ['type','mode','tty','address','control_type','title','show_plot','use_cache','label'];
    $self->{'_desc'} = {
        'mode' => 'Device control mode',
        'tty' => 'Device communication device (ex. ttyS0)',
        'address' => 'Device address',
	'control_type' => 'Device control type',
	'title' => 'Optional title',
	'label' => 'As multiplexers can only have integers as name this tag can be used to give the device a meaningfull name',
        'use_cache'=>'Determines if persisten caching of read values is allowed',
    };
    $self->{'_default'} = {
        'mode' => 'Automatic',
        'tty' => 'ttyS0',
        'address' => 0,
	'control_type' => 'ICP',
        'use_cache'=>'No',
    };
    $self->{'_values_type'} = [(RFC::Device::list_Multiplexer())];
    $self->{'_values_control_type'} = [(RFC::Device::list_RELAY())];
    $self->{'_section'} = 'gas_multiplexer_'.$self->name;
    bless $self,$class;
    $self->load_conf();
    $self->{'_type'} = 'Gas multiplexer';
    $self->{'_file'} = SemaforeFile->new($userdir.'rig'.$self->rig->name.'/multiplex_'.$self->name,'/home/celltest/Gas_rig'.$self->rig->name.'.lock');
    unless ($self->{'_file'}->exist)
    {
        ### Create file with rw for all           ###
        ### Yes I know 666 is the number of evil! ###
        $self->{'_file'}->lock_ex;
        $self->{'_file'}->writeline('');
        chmod 0666, $self->{'_file'}->filename;
        $self->{'_file'}->unlock;
    }
    $self->{'_info_string'} = 'Note that gas multiplexers are not normally required to be enabled to work properly and be controlable from the user interface as they are enabled through the MFC device they are attatched to';
    return $self->init;
}

sub DESTROY
{
    my $self = shift;
    $self->{'_file'}->DESTROY() if ($self->{'_file'});
    return undef;
}

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

sub attatch_controler($)
{
    my $self = shift;
    $self->{'_controler'} = shift;
    return $self->init;
}

sub controler($)
{
    my $self = shift;
    return $self->{'_controler'};
}

sub init
{
    my $self = shift;
    $self->{'_gasses'} = [];
    $self->{'_channels'} = {};
    $self->{'_gasses'} = [$self->{'_controler'}->gasses] if ($self->{'_controler'});
    if ($self->mode =~ /auto/i)
    {
        my $type = $self->get_conf('control_type');
        $type = 'ICP' unless $type;
	foreach my $gas (@{$self->{'_gasses'}})
	{
	    my $dev = $self->rig->get_device('relay',$self->get_conf($gas.'_device_name'));
	    unless ($dev)
	    {
		$dev = $self->rig->init_device('relay',$self->get_conf($gas.'_device_name'));
		$dev->readstring_ignore(1) if ($dev);
	    }
	    unless ($dev)
	    {
		$dev = RFC::Device::new_RELAY($self->rig,'multiplex_'.$self->name.'_'.$gas,$type,$self->{'_tty'},$self->{'_address'},$self->channel($gas));
		$dev->readstring_ignore(1) if ($dev);
	    }
	    $self->die("Eerror: Could not initialise relay device for gas $gas") unless ($dev);
	    $self->{'_channels'}{$gas} = $dev;
	    $dev->set_readonly(1);
	}
    }
    else
    {
	foreach my $gas (@{$self->{'_gasses'}})
	{
	    my $dev = RFC::Device::new_RELAY($self->rig,'multiplex_'.$self->name.'_'.$gas,'Manual');
	    $dev->readstring_ignore(1);
	    $self->{'_channels'}{$gas} = $dev;
	    $dev->set_readonly(1);
	}
    }
    return $self;
}


sub relays()
{
    my $self = shift;
    my @list = ();
    foreach my $key (keys %{$self->{'_channels'}})
    {
	push @list,$self->{'_channels'}{$key};
    }
    return @list;
}

sub test()
{
    my $self = shift;
    my $ok = 1;
    foreach my $g (@{$self->{'_gasses'}})
    {
	$ok = 0 unless ($self->{'_channels'}{$g}->test);
    }
    return $ok;
}


sub Accept
{
    my $self = shift;
    my $visitor= shift;
    return undef if ($self->{'_seen_visitors'}{$visitor});
    foreach my $g (@{$self->{'_gasses'}})
    {
	$self->{'_channels'}{$g}->Accept($visitor) if ($self->{'_channels'}{$g});
    }
    $visitor->VisitComplex($self);
    $self->{'_seen_visitors'}{$visitor} = 1;
    return undef;
}


sub test_string()
{
    my $self = shift;
    my $string = 'Multiplexer '.$self->name.': ';
    my @arr = ();
    my $data = RFC::Cache->new();
    foreach my $g (@{$self->{'_gasses'}})
    {
	push @arr,$g.' => '.$self->{'_channels'}{$g}->read($data);
    }
    $string .= join(', ',@arr)."\n";
    return $string;
}


sub monitor()
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
	$data = RFC::Cache->new();
	$data->debug($self->debug()) if ($self->debug);
    }
    my @arr = ();
    foreach my $g (@{$self->{'_gasses'}})
    {
	push @arr,$g.' => '.$self->{'_channels'}{$g}->read($data);
    }
    return join(', ',@arr)."\n";
}

sub gasses()
{
    my $self = shift;
    $self->init() if (!@{$self->{'_gasses'}} && $self->{'_controler'});
    return @{$self->{'_gasses'}};
}

sub gas_all()
{
    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 $var = $data->get('Gas_multiplex_gas_all',$self->name);
    if (defined($var))
    {
	my @gas = split(/,/,$var);
	$self->writedebug("Returning already cached value for gas_all: '$var' for $self") if ($self->debug > 1);
	return @gas;
    }
    my @gas = ();
    foreach my $g (@{$self->{'_gasses'}})
    {
	my $status = $self->{'_channels'}{$g}->read($data);
	next unless ($status == 1);  ## if read returns -1 it is an error value ##
	my $gdev = $self->rig->get_device('gas',$g);
	push @gas, $gdev->gas() if ($gdev);
    }
    unless (@gas == 1)
    {
	$self->writedebug("Retrying as no gas appears to be selected!");
	@gas = ();
	## Retry if communication failed ##
	my $d2 = RFC::Cache->new();
	foreach my $g (@{$self->{'_gasses'}})
	{
	    my $status = $self->{'_channels'}{$g}->read($d2);
	    next unless ($status == 1);   ## if read returns -1 it is an error value ##
	    my $gdev = $self->rig->get_device('gas',$g);
	    push @gas, $gdev->gas() if ($gdev);
	}
    }
    unless (@gas)
    {
	my $str = 'Warning: No gas was engaged for multiplexer '.$self->name.' for rig '.$self->rig->name;
        $self->rig->warning_mail($self,$str);
	$self->writedebug($str);
	return undef;
    }
    if (@gas > 1)
    {
	my $str = 'Alert! More than one gas is selected for multiplexer '.$self->name.' for rig ' . $self->rig->name . '. Selected gasses: '.join(', ',@gas);
        $self->rig->warning_mail($self,$str);
	$self->writedebug($str);
	return undef;
    }
    $self->writedebug("Setting cache for gas_all for $self to '".join(',',@gas)."'") if ($self->debug);
    $data->set('Gas_multiplex_gas_all',$self->name,join(',',@gas));
    return @gas;
}

sub gas()
{
    my $self = shift;
    my @list = $self->gas_all(@_);
    return $list[0];
}

sub gas_all_name()
{
    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 @gas = ();
    my $var = $data->get('Gas_multiplex_gas_name',$self->name);
    if (defined($var))
    {
	my @gas = split(/,/,$var);
	$self->writedebug("Returning already cached value for gas_all_name: '$var' for $self") if ($self->debug > 1);
	return @gas;
    }
    foreach my $g (@{$self->{'_gasses'}})
    {
	my $status = $self->{'_channels'}{$g}->read($data);
	next unless ($status == 1);  ## if read returns -1 it is an error value ##
	my $gdev = $self->rig->get_device('gas',$g);
	push @gas, $gdev->name() if ($gdev);
    }
    unless (@gas == 1)
    {
	$self->writedebug("Retrying as no gas appears to be selected!");
	@gas = ();
	## Retry if communication failed ##
	my $d2 = RFC::Cache->new();
	foreach my $g (@{$self->{'_gasses'}})
	{
	    my $status = $self->{'_channels'}{$g}->read($d2);
	    next unless ($status == 1);   ## if read returns -1 it is an error value ##
	    my $gdev = $self->rig->get_device('gas',$g);
	    push @gas, $gdev->name() if ($gdev);
	}
    }
    unless (@gas)
    {
	my $str = 'Warning: No gas was engaged for multiplexer '.$self->name.' for rig '.$self->rig->name;
        $self->rig->warning_mail($self,$str);
	$self->writedebug($str);
	return undef;
    }
    if (@gas > 1)
    {
	my $str = 'Alert! More than one gas is selected for multiplexer '.$self->name.' for rig ' . $self->rig->name . '. Selected gasses: '.join(', ',@gas);
        $self->rig->warning_mail($self,$str);
	$self->writedebug($str);
	return undef;
    }
    $self->writedebug("Setting cachr for gas_all_name for $self to '".join(',',@gas)."'") if ($self->debug);
    $data->set('Gas_multiplex_gas_name',$self->name,join(',',@gas));
    return @gas;
}

sub gas_name()
{
    my $self = shift;
    my @list = $self->gas_all_name(@_);
    return $list[0];
}

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 $res = undef;
    my @t = $self->get_time();
    my $t = join(':',@t);
    my @gas = $self->gas_all_name($data);
    my $gas = 'NONE';
    $gas = join(',',@gas) if (@gas);
    $gas = 'NA' unless ($gas && $gas =~ /\w/);
    my $label = $self->label();
    $label =~ s/\W+/_/g;
    return join(' ',$label.'_selected_gas',$t,time,$gas);
#    return join(' ',$self->name.'_selected_gas',$t,time,$gas);
}

sub read_nocache
{
    my $self = shift;
    my $gas = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    $self->check_cache($data);
    unless ($gas)
    {
	return $self->gas($data);
    }
    unless (exists($self->{'_channels'}{$gas}))
    {
	$self->writedebug("Warning, None of the gasses for multiplexer ".$self->name." was found matching gas $gas for rig ".$self->rig->name);
	return undef;
    }
    my $res = $self->{'_channels'}{$gas}->read($data);
    $self->writedebug("Status for multiplexer ".$self->name." for gas $gas on channel ".$self->channel($gas)." was $res") if ($self->debug > 2);
    $res = 0 unless (defined($res));
    my $str = time .' '.$self->name. ' '.$res;
    $self->rig->kanaldata($str) unless ($self->readstring_ignore);
    $self->set_cache($res);
    $self->{'_last_read'} = Time::HiRes::time;
    return $res;
}

sub set()
{
    my $self = shift;
    my $gas = shift;
    $self->writedebug("Setting multiplexer ".$self->name." to $gas") if ($self->debug > 2);
    unless (exists($self->{'_channels'}{$gas}))
    {
	$self->die("Error: None of the gasses for multiplexer ".$self->name." was found matching gas $gas for rig ".$self->rig->name);
	return undef;
    }
    my $data = RFC::Cache->new();
    foreach my $g (@{$self->{'_gasses'}})
    {
	my $status = $self->{'_channels'}{$g}->read($data);
	## Only set relays to 0 for engaged relays (saves serial bus bandwidth) ##
	next if ($status == 0 || $g eq $gas);
	$self->{'_channels'}{$g}->set(0,@_);
    }
    $self->{'_channels'}{$gas}->set(1,@_);
    $self->rig->change_config_value('gas_'.$gas,'mode','Automatic');
    ### Set gas flows for not engaged gasses to 0 and mark as manual ###
    $self->writedebug("Setting non engaged gas flows to 0") if ($self->debug > 2);
    foreach my $g (@{$self->{'_gasses'}})
    {
	next if ($g eq $gas);
	$self->rig->change_config_value('gas_'.$g,'mode','Manual');
	my $dev = $self->rig->get_device('gas',$g);
	if ($dev)
	{
	    $dev->setflow(0,@_);
	}
	else
	{
	    $self->errorlog("Error: could not get device for gas $gas when changing multiplexer ".$self->name,$_[0]);
	}
    }
    ### Set gas for controler ###
    unless ($self->{'_controler'})
    {
	foreach my $d ($self->rig->list_devices('MFC'))
	{
	    my $dev = $self->rig->get_device('MFC',$d);
	    $self->{'_controler'} = $d if ($dev && $dev->multiplexer == $self->name);
	}
    }
    $self->rig->change_config_value('controler_'.$self->{'_controler'}->name,'gas',$gas) if ($self->{'_controler'});
    ### Reset error lock ###
    $self->rig->unset_warning($self);
    $self->notify('set',$gas,@_);
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Mode: '.$self->mode;
    push @arr,'Type: '.$self->type;
    push @arr,'TTY: '.$self->{'_tty'};
    push @arr,'Address: '.$self->{'_address'};
    my @chs = ();
    foreach my $g (@{$self->{'_gasses'}})
    {
	my $dev = $self->rig->init_device('relay',$self->get_conf($g.'_device_name'));
	if ($dev)
	{
	    push @chs,$g.'->'.$dev->print_config;
	}
	else
	{
	    push @chs,$g.'->'.$self->channel($g);
	}
    }    
    push @arr,'channels: '.join(',',@chs);
    return join (', ',@arr);
}


sub channel($)
{
    my $self = shift;
    my $gas = shift;
    my $ch = $self->get_conf($gas);
    return $ch;
}


###########################################################################
##### The Multiplex_slave device is intended if a single multiplexer   ####
##### is used to feed gas to multiple MFC's so that only one is used   ####
##### to select gas but the others follow the master                   ####
###########################################################################


package RFC::Multiplex_slave;
our @ISA = qw(RFC::Multiplex);
sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    bless $self,$class;
    $self->{'_setup_tags'} = ['type','mode','title','master_device','show_plot','use_cache','label'];
    $self->{'_legacy_tags'} = [];
    $self->load_conf();
    $self->{'_type'} = 'Slave multiplexer';
    $self->{'_readonly'} = 1;
    if ($self->{'_master_device'})
    {
	$self->{'_master'} = $self->rig->get_device('multiplex',$self->{'_master_device'});
	$self->{'_master'} = $self->rig->init_device('multiplex',$self->{'_master_device'}) unless ($self->{'_master'});
	$self->die("VICI multiplexers can not be used as master devices for slaves") if ($self->{'_master'} && $self->{'_master'}->type eq 'VICI');
    }
    my @list = $self->rig->avaliable_devices('multiplex');
    my @l = ();
    foreach my $n (@list)
    {
	next if ($n eq $name);
	push @l,$n;
    }
    $self->{'_values_master_device'} = \@l;
    push @{$self->{'_reload_tags'}},'master_device';
    return $self->init();
}


sub init
{
    my $self = shift;
    $self->{'_gasses'} = [];
    $self->{'_channels'} = {};
    $self->{'_gasses'} = [$self->{'_controler'}->gasses] if ($self->{'_controler'});
    if ($self->{'_master'})
    {
	$self->{'_master_gasses'} = [$self->{'_master'}->gasses(@_)];
	foreach my $gas (@{$self->{'_gasses'}})
	{
	    my $key = 'gas_link_'.$gas;
	    $self->{'_gas_link'}{$gas} = $self->get_conf($key);
	}	
	$self->{'_master'}->attatch_observer(\$self);
    }
    return $self;
}

sub gas()
{
    my $self = shift;
    my @mgas = $self->{'_master'}->gas(@_);
    my $gas = undef;
    foreach my $g (@{$self->{'_gasses'}})
    {
	next unless ($self->{'_gas_link'}{$g} eq $mgas[0]);
	$gas = $g;
	last;
    }	
    if ($gas)
    {
	my $dev= $self->rig->get_device('gas',$gas);
        return $dev->gas(@_) if ($dev);
    }
    return undef;
}

sub gas_name()
{
    my $self = shift;
    my @mgas = $self->{'_master'}->gas(@_);
    my $gas = undef;
    foreach my $g (@{$self->{'_gasses'}})
    {
        next unless ($self->{'_gas_link'}{$g} eq $mgas[0]);
        $gas = $g;
        last;
    }
    if ($gas)
    {
        my $dev = $self->rig->get_device('gas',$gas);
        return $dev->name(@_) if ($dev);
    }
    return undef;
}


sub gas_all_name()
{
    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 $gas = $self->gas($data);
    my $dev = $self->rig->get_device('gas',$gas);
    return $dev->name() if ($dev);
    return undef;
}

sub gas_all()
{
    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 $gas = $self->gas($data);
    my $dev = $self->rig->get_device('gas',$gas);
    return $dev->gas if ($dev);
    return undef;
}

sub read_nocache
{
    my $self = shift;
    my $gas = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    return $self->gas($data);
}

sub monitor()
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
	$data = RFC::Cache->new();
	$data->debug($self->debug()) if ($self->debug);
    }
    my @arr = ();
    my $gas = $self->gas($data);
    foreach my $g (@{$self->{'_gasses'}})
    {
	my $status = 0;
	$status = 1 if ($g eq $gas);
	push @arr,$g.' => '.$status;
    }
    return join(', ',@arr)."\n";
}

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


sub observe()
{
    my $self = shift;
    my $action = shift; ## Must always be 'set';
    my $value = shift;
    $self->set($value) if ($action eq 'set');
}

sub set()
{
    my $self = shift;
    my $master_gas = shift;
    my $gas = undef;
    foreach my $g (@{$self->{'_gasses'}})
    {
	next unless ($self->{'_gas_link'}{$g} eq $master_gas);
	$gas = $g;
	last;
    }	
    unless ($gas)
    {
	$self->die("Error: None of the gasses for multiplexer ".$self->name." was found matching gas $gas for rig ".$self->rig->name);
	return undef;
    }
    $self->writedebug("Setting multiplexer ".$self->name." to $gas") if ($self->debug > 2);
    my $data = RFC::Cache->new();
    $self->rig->change_config_value('gas_'.$gas,'mode','Automatic');
    ### Set gas flows for not engaged gasses to 0 and mark as manual ###
    $self->writedebug("Setting non engaged gas flows to 0") if ($self->debug > 2);
    foreach my $g (@{$self->{'_gasses'}})
    {
	next if ($g eq $gas);
	$self->rig->change_config_value('gas_'.$g,'mode','Manual');
	my $dev = $self->rig->get_device('gas',$g);
	if ($dev)
	{
	    $dev->setflow(0,@_);
	}
	else
	{
	    $self->errorlog("Error: could not get device for gas $gas when changing multiplexer ".$self->name,$_[0]);
	}
    }
    ### Set gas for controler ###
    unless ($self->{'_controler'})
    {
	foreach my $d ($self->rig->list_devices('MFC'))
	{
	    my $dev = $self->rig->get_device('MFC',$d);
	    $self->{'_controler'} = $d if ($dev && $dev->multiplexer == $self->name);
	}
    }
    $self->rig->change_config_value('controler_'.$self->{'_controler'}->name,'gas',$gas) if ($self->{'_controler'});
    ### Reset error lock ###
    $self->rig->unset_warning($self);
    $self->notify('set',$gas,@_);
}

sub readstring_ignore()
{
    my $self = shift;
    ## Slave multiplexers are alays readonly
    return 1;
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Mode: '.$self->mode;
    push @arr,'Type: '.$self->type;
    push @arr,'TTY: '.$self->{'_tty'};
    push @arr,'Address: '.$self->{'_address'};
    my @chs = ();
    foreach my $g (@{$self->{'_gasses'}})
    {
	my $dev = $self->rig->init_device('relay',$self->get_conf($g.'_device_name'));
	if ($dev)
	{
	    push @chs,$g.'->'.$dev->print_config;
	}
	else
	{
	    push @chs,$g.'->'.$self->channel($g);
	}
    }    
    push @arr,'channels: '.join(',',@chs);
    push @arr,'Master: '.$self->{'_master'}->name() if ($self->{'_master'});
    return join (', ',@arr);
}


sub test()
{
    my $self = shift;
    my $ok = 1;
    foreach my $g (@{$self->{'_gasses'}})
    {
        $ok = 0 unless ($self->{'_gas_link'}{$g});
    }
    return $ok;
}


sub test_string()
{
    my $self = shift;
    my $string = 'Slave multiplexer '.$self->name.': ';
    my @arr = ();
    my $data = RFC::Cache->new();
    push @arr,'Selected gas: '.$self->gas_name($data);
    foreach my $g (@{$self->{'_gasses'}})
    {
        push @arr, $g .' => ' . $self->{'_gas_link'}{$g};
    }
    if ($self->{'_master'})
    {
        push @arr, 'Master';
        push @arr, $self->{'_master'}->test_string;
    }
    $string .= join(', ',@arr)."\n";
    return $string;
}

###########################################################################
#### The VICI multiplex device implements an additional member function ###
#### $id->get_channels() which queries the physical device to determine ###
#### which channels are avaliable                                       ###
###########################################################################


package RFC::VICI;
our @ISA = qw(RFC::Multiplex);

sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_controler'} = undef;
    $self->{'_controler'} = shift if (@_);
    $self->{'_setup_tags'} = ['type','tty','communication','baudrate','address','title','show_plot','use_cache','label'];
    $self->{'_legacy_tags'} = [];
    $self->{'_desc'} = {
        'mode' => 'Device control mode',
        'tty' => 'Device communication device (ex. ttyS0)',
        'communication' => 'Device communication protocol',
        'baudrate' => 'Communication speed',
        'address' => 'Device address',
	'title' => 'Optional title',
	'label' => 'As multiplexers can only have integers as name this tag can be used to give the device a meaningfull name',
        'use_cache'=>'Determines if persisten caching of read values is allowed',
    };
    $self->{'_default'} = {
        'mode' => 'Automatic',
	'communication' => 'RS485',
	'baudrate' => 4800,
        'tty' => 'ttyS0',
        'address' => 'Z',
        'use_cache'=>'No',
    };
    $self->{'_values_type'} = [(RFC::Device::list_Multiplexer())];
    $self->{'_values_mode'} = [('Automatic')];
    $self->{'_values_communication'} = [('RS232','RS485')];
    $self->{'_values_baudrate'} = [(4800,9600,19200,38400,57600,115200)];
    $self->{'_selected'} = 0;
    bless $self,$class;
    $self->load_conf();
    $self->{'_type'} = 'VICI';
    $self->{'_set_fn_names'} = ['park']; # List of function names which can trigger a notify event
    $self->{'_ui_fn_names'} = ['park']; # List of function names which can be called from the device setup UI 
    $self->{'_ui_fn_args'} = {}; # Hash with number of args for function listed in _ui_fn_names
    $self->{'_ui_fn_desc'} = {
        'park' => 'Parks the selector in the reference position for exchanging valve installation.',
    };
    return $self->init;
}

sub park
{
    my $self = shift;
    $self->comm('AL');
    return $self->comm('NP');
}

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


sub gas_all()
{
    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 $select = $self->channel();
    my @gas = ();
    foreach my $g (@{$self->{'_gasses'}})
    {
	next unless ($self->{'_channels'}{$g} == $select);
	my $gdev = $self->rig->get_device('gas',$g);
	push @gas, $gdev->gas() if ($gdev);
    }
    unless (@gas)
    {
	my $str = 'Warning: No gas was engaged for multiplexer '.$self->name.' for rig '.$self->rig->name;
        $self->rig->warning_mail($self,$str);
	$self->writedebug($str);
	return undef;
    }
    if (@gas > 1)
    {
	my $str = 'Alert! More than one gas is selected for multiplexer '.$self->name.' for rig ' . $self->rig->name . '. Selected gasses: '.join(', ',@gas);
        $self->rig->warning_mail($self,$str);
	$self->writedebug($str);
	return undef;
    }
    return @gas;
}


sub gas_all_name()
{
    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 @gas = ();
    my $select = $self->channel();
    foreach my $g (@{$self->{'_gasses'}})
    {
	next unless ($self->{'_channels'}{$g} == $select);
	my $gdev = $self->rig->get_device('gas',$g);
	push @gas, $gdev->name() if ($gdev);
    }
    unless (@gas)
    {
	my $str = 'Warning: No gas was engaged for multiplexer '.$self->name.' for rig '.$self->rig->name;
        $self->rig->warning_mail($self,$str);
	$self->writedebug($str);
	return undef;
    }
    if (@gas > 1)
    {
	my $str = 'Alert! More than one gas is selected for multiplexer '.$self->name.' for rig ' . $self->rig->name . '. Selected gasses: '.join(', ',@gas);
        $self->rig->warning_mail($self,$str);
	$self->writedebug($str);
	return undef;
    }
    return @gas;
}


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 $res = undef;
    my @t = $self->get_time();
    my $t = join(':',@t);
    my @gas = $self->gas_all_name($data);
    my $gas = join(',',@gas);
    $gas = 'NONE' unless (@gas);
    my $label = $self->label();
    $label =~ s/\W+/_/g;
    return join(' ',$label,'_selected_gas',$t,time,$gas);
#    return join(' ',$self->name.'_selected_gas',$t,time,$gas);
}

sub gas_name
{
    my $self = shift;
    my $select = $self->channel();
    foreach my $g (@{$self->{'_gasses'}})
    {
	return $g if ($self->{'_channels'}{$g} == $select);
    }
    return undef;
}

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 $gas = $self->gas_name();
    unless (exists($self->{'_channels'}{$gas}))
    {
	$self->writedebug("Warning, None of the gasses for multiplexer ".$self->name." was found matching gas $gas for rig ".$self->rig->name);
	return undef;
    }
    my $str = time .' '.$self->name. ' '.$gas;
    $self->rig->kanaldata($str) unless ($self->readstring_ignore);
    $self->set_cache($gas);
    $self->{'_last_read'} = Time::HiRes::time;
    return $gas;
}

sub set()
{
    my $self = shift;
    my $gas = shift;
    $self->writedebug("Setting multiplexer ".$self->name." to $gas") if ($self->debug > 2);
    unless (exists($self->{'_channels'}{$gas}))
    {
	$self->die("Error: None of the gasses for multiplexer ".$self->name." was found matching gas $gas for rig ".$self->rig->name);
	return undef;
    }
    my $data = RFC::Cache->new();
    $self->{'_selected'} = 0;
    $self->comm(sprintf('GO%02i',$self->{'_channels'}{$gas}));
    my $res = $self->channel();
    unless ($res == $self->{'_channels'}{$gas})
    {
	$self->errorlog("Error: Multiplexer ".$self->name." did not set corect port when changing gas!",$_[0]);
    }
    $self->rig->change_config_value('gas_'.$gas,'mode','Automatic');
    ### Set gas flows for not engaged gasses to 0 and mark as manual ###
    $self->writedebug("Setting non engaged gas flows to 0") if ($self->debug > 2);
    foreach my $g (@{$self->{'_gasses'}})
    {
	next if ($g eq $gas);
	$self->rig->change_config_value('gas_'.$g,'mode','Manual');
	my $dev = $self->rig->get_device('gas',$g);
	if ($dev)
	{
	    $dev->setflow(0,@_);
	}
	else
	{
	    $self->errorlog("Error: could not get device for gas $gas when changing multiplexer ".$self->name,$_[0]);
	}
    }
    ### Set gas for controler ###
    unless ($self->{'_controler'})
    {
	foreach my $d ($self->rig->list_devices('MFC'))
	{
	    my $dev = $self->rig->get_device('MFC',$d);
	    $self->{'_controler'} = $d if ($dev && $dev->multiplexer == $self->name);
	}
    }
    $self->rig->change_config_value('controler_'.$self->{'_controler'}->name,'gas',$gas) if ($self->{'_controler'});
    ### Reset error lock ###
    $self->rig->unset_warning($self);
    $self->notify('set',$gas,@_);
}


sub comm
{
    my $self = shift;
    my $cmd = shift;
    my $tty = $self->{'_tty'};
    my $adr = $self->{'_address'};
    my $mode = $self->{'_communication'};
    my $baud = $self->{'_baudrate'};
    my $cmdstr = '/'.$adr.$cmd;
    $cmdstr = $cmd if ($mode && $mode =~ /232/);
    my $res = SocketClient::serial_client_raw($self->{'_tty'},'raw',$baud,'none',8,1,$cmdstr);
    unless (defined($res) && $res =~ /\w/)
    {
	select(undef,undef,undef,0.5);
	$res = SocketClient::serial_client_raw($self->{'_tty'},'raw',$baud,'none',8,1,$cmdstr);
    }
    return $res;
}

sub get_channels
{
    my $self = shift;
    my $res = $self->comm('NP');
    if ($res && $res =~ /NP(\d+)/)
    {
	return $1+0;
    }
    return 0;
}

sub test
{
    my $self = shift;
    my $res = $self->comm('VR');
    return 1 if ($res && $res =~ /\w+/);
    return 0;
}

sub test_string()
{
    my $self = shift;
    my $res = $self->comm('VR');
    chomp $res;
    my @chs = ();
    my $select = $self->channel();
    foreach my $g (@{$self->{'_gasses'}})
    {
	my $is_select = 0;
	$is_select = 1 if (defined($self->{'_channels'}{$g}) && defined($select) && $self->{'_channels'}{$g} == $select);
	push @chs,$g.'->'.$is_select;	
    }    
    return $res."\nchannels: ".join(',',@chs);
}



sub init
{
    my $self = shift;
    $self->{'_gasses'} = [];
    $self->{'_channels'} = {};
    $self->{'_gasses'} = [$self->{'_controler'}->gasses] if ($self->{'_controler'});
    foreach my $gas (@{$self->{'_gasses'}})
    {
	my $ch = $self->get_conf($gas.'_port');
	$self->{'_channels'}{$gas} = $ch;
    }
    return $self;
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Mode: '.$self->mode;
    push @arr,'Type: '.$self->type;
    push @arr,'TTY: '.$self->{'_tty'};
    push @arr,'Communication: '.$self->{'_communication'};
    push @arr,'Baudrate: '.$self->{'_baudrate'};
    push @arr,'Address: '.$self->{'_address'};
    my @chs = ();
    my $select = $self->channel();
    foreach my $g (@{$self->{'_gasses'}})
    {
	my $is_select = 0;
	$is_select = 1 if (defined($self->{'_channels'}{$g}) && defined($select) && $self->{'_channels'}{$g} == $select);
	push @chs,$g.'->'.$is_select;
    }    
    push @arr,'channels: '.join(',',@chs);
    return join (', ',@arr);
}


sub monitor()
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    my @arr = ();
    my $select = $self->channel();
    foreach my $g (@{$self->{'_gasses'}})
    {
	my $is_select = 0;
	$is_select = 1 if ($self->{'_channels'}{$g} == $select);
	push @arr,$g.' => '.$is_select;	
    }    
    return join(', ',@arr);
}


sub channel($)
{
    my $self = shift;
    my $gas = shift;
    return $self->{'_selected'} if ($self->{'_selected'});
    my $res = $self->comm('CP');
    if ($res && $res =~ /CP(\d+)/)
    {
	$self->{'_selected'} = $1+0;
    }
    return $self->{'_selected'};
}



package RFC::Selector;
our @ISA = qw(RFC::Multiplex);

sub new()
{
    my $class = shift;
    my $name = shift;
    my $rig = shift;
    my $self = $class->SUPER::new($name,$rig);
    $self->{'_type'} = 'Selector device';
    $self->{'_html_template'} = 'Multiplex.tt';
    $self->{'_setup_tags'} = ['type','title','show_plot','positions','label'];
    $self->{'_legacy_tags'} = [];
    $self->{'_desc'} = {
	'label' => 'As multiplexers can only have integers as name this tag can be used to give the device a meaningfull name',
        'positions' => 'Number of positions',
        'title' => 'Optional title',
        'use_cache'=>'Determines if persisten caching of read values is allowed',
    };
    $self->{'_default'} = {
	'positions' => 2,
    };
    $self->{'_values_positions'} = [(2,3,4,5,6,7,8,9)];
    $self->{'_info_string'} = 'Selector devices only work if they are enabled';
    bless $self,$class;
    return $self->init();
}

sub init()
{
    my $self = shift;
    $self->load_conf();
    return $self;
}


sub test()
{
    my $self = shift;
    return 1;
}

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


sub test_string()
{
    my $self = shift;
    my $string = 'Multiplexer '.$self->name.': ';
    my @arr = ();
    my $data = RFC::Cache->new();
    push @arr,'Selected value: '.$self->gas();
    $string .= join(', ',@arr)."\n";
    return $string;
}

sub monitor()
{
    my $self = shift;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
        $data = RFC::Cache->new();
        $data->debug($self->debug()) if ($self->debug);
    }
    return $self->gas();
}

sub gasses()
{
    my $self = shift;
    my @l = ();
    for (my $x = 0; $x < $self->{'_positions'}; $x++)
    {
	push @l,$x;
    }
    return @l;
}

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


sub read()
{
    my $self = shift;
    my @l = $self->{'_file'}->readlines();
    my $val = $l[0];
    chomp $val;
    return $val;
}

sub gas
{
    my $self = shift;
    return $self->read();
}

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

sub readstring()
{
    my $self = shift;
    return undef if ($self->readstring_ignore);
    my @t = $self->get_time();
    my $t = join(':',@t);
    my $gas = $self->gas();
    my $label = $self->label();
    $label =~ s/\W+/_/g;
    return join(' ',$label.'_selected_gas',$t,time,$gas);
}

sub read_nocache
{
    my $self = shift;
    return $self->gas();
}

sub set()
{
    my $self = shift;
    my $gas = shift;
    $self->writedebug("Setting multiplexer ".$self->name." to $gas") if ($self->debug > 2);
    $self->{'_file'}->writeline($gas);
    $self->proglog("Setting value $gas for Selector ".$self->name,@_);
    $self->notify('set',$gas,@_);
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'Mode: '.$self->mode;
    push @arr,'Selected value: '.$self->gas();
    return join (', ',@arr);
}

sub channel($)
{
    my $self = shift;
    my $gas = shift;
    return undef;
}

return 1;
