package RFC::MFC;
#$Id: MFC.pm,v 1.260 2025/05/21 07:00:17 sqko Exp $

####################################################################
#
#
#     RFC::MFC
#
# This class defines the behaviour of mass flow controlers for gas
# control. To obtain a MFC instance call one of the constructors
# as shown below:
#
# $id = RFC::MFC->new($name,$rig);
#
# $id = RFC::Analog->new($name,$rig);
#
# $id = RFC::AnalogReadonly->new($name,$rig);
#
# $id = RFC::Brooks->new($name,$rig);
#
# $id = RFC::BrooksReadonly->new($name,$rig);
#
#
# All MFC objects have the following member functions in addition to those
# derived from RFC::BaseDevce and Debug.pm:
#
# $id->gasses()         Returns a list of possible gasses 
#                       used if more than one gas can be selected, usually in
#                       conjunction with a RFC::Multiplexer device.
# $id->selected_gas()   Returns the name of the selected gas.
# $id->maxflow()        Returns the maximum possible flow for the selected gas.
# $id->setflow($[$][$]) Sets the gas flow for the mass flow controler to the 
#                       specified value.
#                       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->gas_change()     Returns the gas change mode (either manual or automatic).
# $id->controler_mode() Returns the controler mode (either 'Flowrate' or 'Pressure
#                       Dependiong on if the MFC is a flow controler or a pressure 
#                       controler) 
# $id->change_gas($)    Changes the gas of the controler to the specified 
#                       type.
# $id->gasses()         Returns a list of possible gasses for the device
#                       This is to be used in conjunction with a RFC::Multiplex
#                       device. 
# $id->multiplexer()    Returns the multiplexer deivce if it exists.
# $id->accuracy()       Returns the expected accuracy of the device
#                       (For Brooks MFC's this is usually 1 percent of fullscale).
# $id->read_raw()       Returns the flow rate reported by the controler.
#                       As opposed to the normal read() function the read_raw()
#                       does not check for multiplexer operation. Thus use of this
#                       function directly can result in inconsistently reported
#                       flow rates if the caller does not explicitly check for
#                       multiplexer status.
#
####################################################################



use strict;
use warnings;
use RFC::Main;
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 $self = $class->SUPER::new(@_);
    $self->{'_mode'} = 'Manual';
    $self->{'_type'} = 'Manual';
    $self->{'_calibrated_maxflow'} = 100;
    $self->{'_gasses'} = ['n2'];
    $self->{'_gas'} = 'n2';
    $self->{'_accuracy'} = 0.01;
    $self->{'_gas_change'} = 'Manual';
    $self->{'_multiplexer'} = undef;
    $self->{'_values_gas_change'} = ['Manual','Automatic'];
    $self->{'_setup_tags'} = ['mode','calibrated_gas','calibrated_maxflow','gas','gas_change','gas_multiplexer','gasses','spline','setflow_factor','read_factor','title','show_plot','use_cache'];
    $self->{'_reload_tags'} = ['mode','tty','address','relay','output_control_relay'];
    $self->{'_hide_tags'} = [];
    $self->{'_desc'} = {
        'setflow_factor' => 'Callibration factor for settig flow',
        'read_factor' => 'Callibration factor for reading flow',
	'spline' => 'Spline interpolation table for correction according to callibration sheet if any (leave blank if no callibration)',
	'mode' => 'Device control mode',
	'calibrated_gas' => 'The gas for which the maxflow is specified on the controler (usually nitrogen)',
	'calibrated_maxflow' => 'Maximum flowrate for the callibrated gas (Note in L/hour!)',
	'gas' => 'selected gas',
	'controler_mode' => 'Controler mode of operation',
	'gas_change' => 'Gas change mode',
	'gas_multiplexer' => 'Name of gas multiplexer device if any',
	'gasses' => 'Avaliable gasses, Must contain a comma separated list of gas names (device name!) that the MFC can be used to control (in case of no multiplexer, just the single gas connected).',
	'title' => 'Optional title',
        'use_cache'=>'Determines if persisten caching of read values is allowed',
    };
    $self->{'_values_mode'} = ['Manual'];
    $self->{'_values_controler_mode'} = ['Flowrate'];
    $self->{'_default'} = {
	'gas_change' => 'Manual',
	'calibrated_gas' => 'n2',
	'calibrated_maxflow' => 100,
	'gas' => 'n2',
	'gasses' => 'n2',
	'controler_mode' => 'Flowrate',
	'mode' => 'Manual',
        'show_plot'=>'Yes',
        'setflow_factor' => 1,
        'read_factor' => 1,
        'use_cache'=>'No',
    };
    $self->{'_set_fn_names'} = ['setflow']; # List of function names which can trigger a notify event
    $self->{'_values_relay'} = ['YES','NO'];
    $self->{'_values_output_control_relay'} = ['YES','NO'];
    $self->{'_values_gas_change'} = ['Manual','Automatic'];
    $self->{'_values_gas_multiplexer'} = ['',$self->rig->avaliable_devices('multiplex')];
    $self->{'_values_gas'} = ['',$self->rig->avaliable_devices('gas')];
    $self->{'_values_calibrated_gas'} = ['',RFC::Main::get_gasses()];
    $self->{'_values_relay_type'} = [(RFC::Device::list_RELAY())];
    $self->{'_values_relay_name'} = ['',$self->rig->avaliable_devices('relay')];
    $self->{'_values_control_relay_name'} = ['',$self->rig->avaliable_devices('relay')];
    $self->{'_values_control_relay_type'} = [(RFC::Device::list_RELAY())];
    $self->{'_set_names'} = ['setflow'];
    $self->{'_file'} = SemaforeFile->new($userdir.'rig'.$self->rig->name.'/MFC_'.$self->name,'/home/celltest/Gas_rig'.$self->rig->name.'.lock');
    unless (-e $self->{'_file'}->filename)
    {
        ### Create file with rw for all           ###
        ### Yes I know 666 is the number of evil! ###
        $self->{'_file'}->lock_ex;
        $self->{'_file'}->writeline('0.00');
        chmod 0666, $self->{'_file'}->filename;
        $self->{'_file'}->unlock;
    }
    $self->{'_section'} = 'controler_'.$self->name;
    $self->{'_title'} = $self->get_conf('title');
    bless $self,$class;
    $self->init();
    $self->{'_info_string'} = 'Note that MFC devices are not normally required to be enabled for them to work properly as they are enabled as part of the gas for which they are configured';
    $self->{'_controler_mode'} = 'Flowrate';
    return $self;
}

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

sub controler_mode()
{
    my $self = shift;
    return $self->{'_controler_mode'};
}

sub accuracy()
{
    my $self = shift;
    return $self->maxflow(@_) * $self->{'_accuracy'};
}

sub Accept
{
    my $self = shift;
    my $visitor= shift;
    return undef if ($self->{'_seen_visitors'}{$visitor});
    $self->{'_relay'}->Accept($visitor) if (exists($self->{'_relay'}) && $self->{'_relay'} && ref($self->{'_relay'}) =~ /RFC/ );
    $self->{'_control_relay'}->Accept($visitor) if (exists($self->{'_control_relay'}) && $self->{'_control_relay'} && ref($self->{'_control_relay'}) =~ /RFC/);
    $self->{'_multiplexer'}->Accept($visitor) if (exists($self->{'_multiplexer'}) && $self->{'_multiplexer'} && ref($self->{'_multiplexer'}) =~ /RFC/);
    $visitor->VisitComplex($self);
    $self->{'_seen_visitors'}{$visitor} = 1;
    return undef;
}


sub test_string()
{
    my $self = shift;
    my $flow = $self->read(@_);
    my $string = "Flow for controler ".$self->name." was $flow L/hour\n";
    $string .= $self->{'_relay'}->test_string(@_)."\n" if (exists($self->{'_relay'}) && $self->{'_relay'} && ref($self->{'_relay'}) =~ /RFC/ );
    $string .= $self->{'_control_relay'}->test_string(@_)."\n" if (exists($self->{'_control_relay'}) && $self->{'_control_relay'} && ref($self->{'_control_relay'}) =~ /RFC/);
    $string .= $self->{'_multiplexer'}->test_string(@_)."\n" if (exists($self->{'_multiplexer'}) && $self->{'_multiplexer'} && ref($self->{'_multiplexer'}) =~ /RFC/);
    return $string;
}

sub selected_gas()
{
    my $self = shift;
    return $self->{'_gas'};
}

sub gasses()
{
    my $self = shift;
    return @{$self->{'_gasses'}} if (@{$self->{'_gasses'}});
    return $self->{'_gas'};
}

sub gas_change()
{
    my $self = shift;
    return $self->{'_gas_change'};
}

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

sub init()
{
    my $self = shift;
    my @hide_tags;
    foreach my $tag ('gas','calibrated_gas','calibrated_maxflow','spline','controler_mode')
    {
	$self->{'_'.$tag} = $self->get_default($tag);
        my $conf = $self->get_conf($tag);
        $self->{'_'.$tag} = $conf if (defined($conf) && $conf =~ /\w/);
    }
    my $gasstr = $self->get_conf('gasses');
    unless ($gasstr)
    {
	$gasstr = $self->{'_gas'}; 
	$self->{'_default'}{'gasses'} = $gasstr;
    }
    my @gasses = split(/\s*\,\s*/,$gasstr);
    $self->{'_gasses'} = \@gasses;
    $self->{'_gas_change'} = $self->get_conf('gas_change');
    if ($self->{'_gas_change'} && $self->{'_gas_change'} =~ /auto/i)
    {
	### Register Multiplexer from rig if present ###
	my $mult = $self->get_conf('gas_multiplexer');
	$self->die("No multiplexer name specified for MFC ".$self->name()) unless ($mult);
	my $device = $self->rig->get_device('multiplex',$mult);
	unless ($device)
	{
	    $device = $self->rig->init_device('multiplex',$mult);
	    $device->readstring_ignore(1) if ($device);
	}
	if ($device)
	{
	    $self->{'_multiplexer'} = $device->attatch_controler($self);
	}
	else
	{
	    my $type = $self->get_conf('type');
	    $type = 'ICP' unless ($type);
	    my $dev = RFC::Device::new_Multiplexer($self->rig,$mult,$type,$self);
	    $self->die("No multiplexer found for MFC ".$self->name) unless ($dev);
	    $self->{'_multiplexer'} = $dev;
	    $dev->readstring_ingore(1) if ($dev);
	    $self->rig->register_device('multiplex',$dev);
	}
    }
    $self->{'_hide_tags'} = \@hide_tags;
    return $self;
}

sub maxflow()
{
    my $self = shift;
    return $self->{'_calibrated_maxflow'};
}

sub read_raw()
{
    my $self = shift;
    my $flow = 0;
    my $data = RFC::Cache->new();
    $data = shift if (@_);
    my $var = $data->get('manual_MFC',$self->name);
    return $var if (defined($var));
    my @lines = $self->{'_file'}->readlines;
    $flow = $lines[0] if (@lines);
    $data->set('manual_MFC',$self->name,$flow);
    return $flow;
}

sub read_nocache
{
    my $self = shift;
    my $flow = $self->read_raw(@_);
    if ($self->{_multiplexer})
    {
	my $mgas = $self->{'_multiplexer'}->gas_name(@_);
	unless ($mgas && $mgas eq $self->{'_gas'})
	{
	    my $str = "Warning, Gas multiplexer not engaged as expected (wrong gas or no gas selected), gas flow possibly wrong! Setting flow to 0!";
	    $self->writedebug($str);
	    $flow = 0;
	}
    }
    my $str = time .' MFC_'.$self->name.' '.$flow;
    $self->rig->kanaldata($str) unless ($self->readstring_ignore);
    $self->set_cache($flow);
    $self->{'_last_read'} = Time::HiRes::time;
    return $flow;
}

sub setflow($)
{
    my $self = shift;
    my $flow = shift;
    $self->{'_file'}->writeline(sprintf("%.4f",$flow));
    $self->proglog("Flow for MFC ".$self->name." set to $flow L/hour manually",@_);
    $self->notify('setflow',$flow,@_);
    return $flow;
}

sub test()
{
    my $self = shift;
    if ($self->{'_multiplexer'})
    {
	return 0 unless($self->{'_multiplexer'}->test());
    }
    return 1;
}

sub change_gas($)
{
    my $self = shift;
    my $gas = shift;
    my $found = 0;
    foreach ($self->gasses(@_))
    {
	next unless ($_ eq $gas);
	$found = $_;
	last;
    }
    $self->die("Gas $gas not found as avaliable for controler ".$self->name) unless ($found);
    if ($self->{'_gas_change'} eq 'auto')
    {
	$self->die('Controler '.$self->name.' set to automatic gas control, but no multiplexer found for controler!') unless ($self->{'_multiplexer'});
	return $self->{'_multiplexer'}->change($gas);
    }
    else
    {
	return 'Gas for controler '.$self->name.' must be changed manually!';
    }
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'MFC type: '.$self->type;
    push @arr,'Mode: '.$self->mode;
    if ($self->{'_multiplexer'})
    {
	push @arr,$self->{'_multiplexer'}->print_config;
    }
    return join (', ',@arr);
}

package RFC::Analog;
our @ISA = qw(RFC::MFC);

sub new()
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'_mode'} = 'Automatic';
    $self->{'_readonly'} = 0;
    $self->{'_type'} = 'Analog';
    $self->{'_tty'} = 'ttyS0';
    $self->{'_accuracy'} = 0;
    $self->{'_channel'} = undef;
    $self->{'_channel_device'} = undef;
    $self->{'_calibrated_gas'} = undef;
    $self->{'_calibrated_maxflow'} = 100;
    $self->{'_gasses'} = ['n2'];
    $self->{'_gas'} = 'n2';
    $self->{'_address'} = 0;
    $self->{'_control_channel'} = 0;
    $self->{'_relay'} = undef;
    $self->{'_relay_type'} = undef;
    $self->{'_relay_time'} = 0;
    $self->{'_control_relay'} = undef;
    $self->{'_control_relay_type'} = undef;
    $self->{'_gas_change'} = 'manual';
    $self->{'_multiplexer'} = undef;
    $self->{'_values_mode'} = ['Automatic'];
    $self->{'_values_output_range'} = ['0-5V','1-5V','0-10V','2-10V','0-20mA','4-20mA'];
    $self->{'_setup_tags'} = ['mode','output_name','channel_name','output_control_relay','control_relay_name','calibrated_gas','calibrated_maxflow','accuracy','output_range','use_underrange_cutoff','gas','relay','relay_time','relay_name','gas_change','gas_multiplexer','gasses','setflow_factor','read_factor','title','show_plot','use_cache'];
    $self->{'_legacy_tags'} = ['mode','output_name','output_type','address','control_channel','tty','channel_name','channel_type','channel','channel_tty','channel_address','output_control_relay','control_relay_name','control_relay_type','control_relay_tty','control_relay_address','control_relay_channel','calibrated_gas','calibrated_maxflow','accuracy','output_range','use_underrange_cutoff','gas','relay','relay_time','relay_name','relay_type','relay_tty','relay_address','relay_channel','gas_change','gas_multiplexer','gasses','setflow_factor','read_factor','title','show_plot','use_cache'];
    $self->{'_desc'} = {
	'mode' => 'Device control mode',
	'calibrated_gas' => 'The gas for which the maxflow is specified on the controler (usually nitrogen)',
	'calibrated_maxflow' => 'Maximum flowrate for the callibrated gas (Note in L/hour!)',
	'controler_mode' => 'Controler mode of operation',
	'gas' => 'selected gas',
	'gas_change' => 'Gas change mode',
	'gas_multiplexer' => 'Name of gas multiplexer device if any',
	'gasses' => 'Avaliable gasses, Must contain a comma separated list of gas names (device name!) that the MFC can be used to control (in case of no multiplexer, just the single gas connected).',
	'title' => 'Optional title',
	'relay' => 'Bypass relay avaliable',
	'relay_name' => 'Bypass relay name',
	'accuracy' => 'Accuracy of the flow rate in percent',
	'relay_tty' => 'Bypass relay tty (used only if no relay name is specified)',
	'relay_address'=> 'Bypass relay address (used only if no relay name is specified)',
	'relay_channel' => 'bypass relay channel (used only if no relay name is specified)',
	'relay_time' => 'bypass relay engage time (seconds)',
	'relay_type' => 'bypass relay device type (used only if no relay name is specified)',
	'control_relay_type' => 'Control relay device type (used only if no control relay name is specified)',
	'control_relay' => 'Cutoff relay avaliable',
	'control_relay_name' => 'Cutoff relay name',
	'control_relay_address' => 'Control relay address (used only if no control relay name is specified)',
	'control_relay_channel' => 'Control relay channel (used only if no control relay name is specified)',
	'control_relay_tty' => 'Control relay tty (used only if no control relay name is specified)',
	'output_control_relay' => 'Switch controling if an external output control device is fitted (for instance a magnetic valve for forcing complete cutoff of gas)',
	'output_name' => 'Name of analog output device (the RFCcontrol device controling the analog output to the physical MFC)',
	'output_type' => 'Output device type (Only used if no output name)',
	'address' => 'Output device address (Only used if no output name)',
	'tty' => 'Output device communication device (Only used if no output name)',
	'output_range' => 'Output range, note that this may be different than the range of the output device!',
	'control_channel' => 'Output device channel (Only used if no output name)',
	'channel_type' => 'Input device type (used only if no channel name is specified)',
	'channel_tty' => 'Input device communication device (used only if no channel name is specified)',
	'channel_address' => 'Input device address (used only if no channel name is specified)',
	'channel_name' => 'Name of the analog input device (The RFCcontrol input device measuring the analog signal from the physical MFC)',
	'channel' => 'Input device channel (used only if no channel name is specified)',
        'spline' => 'Spline interpolation table for correction according to callibration sheet if any (leave blank if no callibration)',
	'setflow_factor' => 'Callibration factor for settig flow',
	'read_factor' => 'Callibration factor for reading flow',
        'use_cache'=>'Determines if persisten caching of read values is allowed',
	'use_underrange_cutoff' => 'Allow the analog output to be set below the operational range in order to force the MFC to close completely',
    };
    $self->{'_default'} = {
	'mode' => 'Automatic',
	'calibrated_gas' => 'n2',
	'calibrated_maxflow' => 100,
	'gas' => 'n2',
	'gasses' => 'n2',
	'gas_change' => 'Manual',
	'relay' => 'NO',
	'accuracy' => 1,
	'relay_type' => 'ICP',
	'relay_name' => '',
	'relay_tty' => 'ttyS0',
	'relay_address'=>1,
	'relay_channel' => 0,
	'relay_time' => 0,
	'control_relay_name' => '',
	'control_relay' => 'NO',
	'control_address' => 1,
	'controler_mode' => 'Flowrate',
	'control_channel' => 0,
	'control_tty' => '',
	'output_type' => 'ICP87024',
	'output_range' => '0-5V',
	'address' => 1,
	'tty' => 'ttyS0',
	'channel' => '1:101',
	'channel_type' => 'Keithley',
	'channel_tty' => 'ttyS0',
	'channel_address' => 1,
	'channel_name' => '',
	'output_control_relay' => 'NO',
	'control_relay_tty' => 'ttyS0',
	'control_relay_address' => 1,
	'control_relay_channel' => 0,
	'control_relay_type' => 'ICP',
        'show_plot'=>'Yes',
        'spline' => '',
	'setflow_factor' => 1,
	'read_factor' => 1,
        'use_cache'=>'No',
	'use_underrange_cutoff' => 'Yes',
    };
    bless $self,$class;
    $self->{'_values_channel_name'} = ['',$self->rig->avaliable_devices('simplechannel')];
    $self->{'_values_output_name'} = ['',$self->rig->avaliable_devices('analog')];
    $self->{'_values_output_type'} = [(RFC::Device::list_AnalogOut())];
    $self->{'_values_channel_type'} = [(RFC::Device::list_SimpleChannel())];
    $self->{'_values_use_underrange_cutoff'} = ['Yes','No'];
    $self->init;
    $self->{'_controler_mode'} = 'Flowrate';
    return $self;
}

sub test()
{
    my $self = shift;
    $self->init_output() unless ($self->{'_output_device'});
    return 0 unless($self->{'_channel_device'}->test());
    return 0 unless($self->{'_output_device'}->test());
    if ($self->{'_relay'})
    {
	return 0 unless($self->{'_relay'}->test());
    }
    if ($self->{'_control_relay'})
    {
	return 0 unless($self->{'_control_relay'}->test());
    }
    if ($self->{'_multiplexer'})
    {
	return 0 unless($self->{'_multiplexer'}->test());
    }
    return 1;
}

sub Accept
{
    my $self = shift;
    my $visitor= shift;
    return undef if ($self->{'_seen_visitors'}{$visitor});
    $self->init_output() unless ($self->{'_output_device'});
    $self->{'_channel_device'}->Accept($visitor);
    $self->{'_output_device'}->Accept($visitor);
    $self->{'_relay'}->Accept($visitor) if (exists($self->{'_relay'}) && $self->{'_relay'});
    $self->{'_control_relay'}->Accept($visitor) if (exists($self->{'_control_relay'}) && $self->{'_control_relay'});
    $self->{'_multiplexer'}->Accept($visitor) if (exists($self->{'_multiplexer'}) && $self->{'_multiplexer'});
    $visitor->VisitComplex($self);
    $self->{'_seen_visitors'}{$visitor} = 1;
    return undef;
}


sub test_string()
{
    my $self = shift;
    $self->init_output() unless ($self->{'_output_device'});
    my $flow = $self->read(@_);
    my $string = "Flow for controler ".$self->name." was $flow L/hour\n";
    $string .= 'Raw measured flow before spline correction: '.$self->{'_raw_flow'}. ', after correction: '.$self->{'_corrected_flow'}."\n" if ($self->{'_spline'});
    $string .= 'Channel device: '.$self->{'_channel_device'}->test_string(@_)."\n";
    $string .= $self->{'_output_device'}->test_string(@_);
    $string .= $self->{'_relay'}->test_string(@_)."\n" if (exists($self->{'_relay'}) && $self->{'_relay'});
    $string .= $self->{'_control_relay'}->test_string(@_)."\n" if (exists($self->{'_control_relay'}) && $self->{'_control_relay'});
    $string .= $self->{'_multiplexer'}->test_string(@_)."\n" if (exists($self->{'_multiplexer'}) && $self->{'_multiplexer'});
    return $string;
}

sub init_output()
{
    my $self = shift;
    my $output_type = $self->get_conf('output_type');    
    my $dev = $self->rig->get_device('analog',$self->{'_output_name'});
    unless ($dev || !$self->{'_output_name'})
    {
	$dev = $self->rig->init_device('analog',$self->{'_output_name'});
	$dev->readstring_ignore(1) if ($dev);
    }
    $self->die("No AnalogOut device named '".$self->{'_output_name'}."' returned by rig ".$self->rig->name) if (!$dev && $self->{'_output_name'});
    my $name = 'MFC_'.$self->name.'_output';
    $dev = $self->rig->get_device('analog',$name) unless ($dev);
    unless ($dev)
    {
	$dev = RFC::Device::new_AnalogOut($self->rig,$name,$output_type,$self->{'_tty'},$self->{'_address'},$self->{'_control_channel'});
	$dev->readstring_ignore(1) if ($dev);
	$self->rig->register_device('analog',$dev);
    }
    $self->{'_output_device'} = $dev;
    return $self;
}


sub init()
{
    my $self = shift;
    my @hide_tags = ();
    foreach my $tag ('tty','address','control_channel','spline','controler_mode','gas','calibrated_gas','calibrated_maxflow','output_range','use_underrange_cutoff','setflow_factor','read_factor','_controler_mode','accuracy')
    {
	$self->{'_'.$tag} = $self->get_default($tag);
        my $conf = $self->get_conf($tag);
        $self->{'_'.$tag} = $conf if (defined($conf) && $conf =~ /\w/);
    }
    if ($self->{'_spline'})
    {
        my @list = split(/\s*\,\s*/,$self->{'_spline'});
	$self->{'_info_string'} = 'This device still uses the deprecated spline interpolation!';
        $self->die("Spline interpolation tables must have at least 2 lines, only one detected!") unless (@list > 1);
    }
    $self->{'_accuracy'} = $self->{'_accuracy'} / 100 if (defined($self->{'_accuracy'}));
    $self->{'_range'} = $self->{'_output_range'};
    ### Delay initialisation of output device untill it is needed ###
    $self->{'_output_name'} = $self->get_conf('output_name');
    push @hide_tags,'channel','output_type','control_channel','address','tty' if ($self->{'_output_name'});
    $self->{'_output_device'} = undef;
    my $type = $self->get_conf('channel_type');
    $type = 'Keithley' unless ($type);
    push @hide_tags,'channel','channel_type','channel_tty','channel_address' if ($self->get_conf('channel_name'));
    my $dev = $self->rig->get_device('simplechannel',$self->rig->get_cv('controler_'.$self->name,'channel_name'));
    unless ($dev || !$self->get_conf('channel_name'))
    {
	$dev = $self->rig->init_device('simplechannel',$self->rig->get_cv('controler_'.$self->name,'channel_name'));
	$dev->readstring_ignore(1) if ($dev);
    }
    $self->die("No simplechannel device named '".$self->get_conf('channel_name')."' returned by rig ".$self->rig->name) if (!$dev && $self->get_conf('channel_name'));
    my $devname = 'MFC_'.$self->name.'_input';
    $dev = $self->rig->get_device('simplechannel',$devname) unless ($dev);
    unless ($dev)
    {
	$self->{'_channel'} = $self->get_conf('channel');
	if ($type !~ /Keithley/i)
	{
	    my $tty = $self->get_conf('channel_tty');
	    my $addr = $self->get_conf('channel_address');
	    $dev = RFC::Device::new_SimpleChannel($self->rig,$devname,$type,$tty,$addr,$self->{'_channel'});
	}
	else
	{
	    $dev = RFC::Device::new_SimpleChannel($self->rig,$devname,$type,$self->{'_channel'});
	}
	$dev->readstring_ignore(1);
	$self->rig->register_device('simplechannel',$dev);
    }
    $self->{'_channel_device'} = $dev;
    my $gasstr = $self->get_conf('gasses');
    unless ($gasstr)
    {
	$gasstr = $self->{'_gas'}; 
	$self->{'_default'}{'gasses'} = $gasstr;
    }
    my @gasses = split(/\s*\,\s*/,$gasstr);
    $self->{'_gasses'} = \@gasses;
    my $relay = $self->get_conf('relay');
    if ($relay && $relay =~ /yes/i)
    {
	$self->{'_relay_name'} = $self->get_conf('relay_name');
	my $dev = $self->rig->get_device('relay',$self->{'_relay_name'});
	unless ($dev || !$self->{'_relay_name'})
	{
	    $dev = $self->rig->init_device('relay',$self->{'_relay_name'});
	    $dev->readstring_ignore(1) if ($dev);
	}
	push @hide_tags,'relay_type','relay_tty','relay_address','relay_channel' if ($dev);
	$self->die("No relay named '".$self->{'_relay_name'}."' returned by rig ".$self->rig->name) if (!$dev && $self->{'_relay_name'});
	my $devname = 'MFC_'.$self->name.'_bypass';
	$dev = $self->rig->get_device('relay',$devname) unless ($dev);
	unless ($dev)
	{
	    my $type = $self->get_conf('relay_type');
	    $type = 'ICP' unless ($type);
	    $self->{'_relay_type'} = $type;
	    my $tty = $self->get_conf('relay_tty');
	    my $addr = $self->get_conf('relay_address');
	    my $ch = $self->get_conf('relay_channel');
	    $dev = RFC::Device::new_RELAY($self->rig,$devname,$type,$tty,$addr,$ch);
	    $self->rig->register_device('relay',$dev);
	    $dev->readstring_ignore(1) if ($dev);
	}
	else
	{
	    $self->{'_relay_type'} = $dev->type;
	}
	$self->{'_relay_time'} = $self->get_conf('relay_time');
	$self->{'_relay'} = $dev;
	$dev->set_readonly() if ($dev);
    }    
    else
    {
	push @hide_tags,'relay_name','relay_type','relay_tty','relay_address','relay_channel','relay_time';
    }
    $relay = $self->get_conf('output_control_relay');
    if ($relay && $relay =~ /yes/i)
    {
	$self->{'_output_relay_name'} = $self->get_conf('control_relay_name');
	my $dev = $self->rig->get_device('relay',$self->{'_output_relay_name'});
	unless ($dev || !$self->{'_output_relay_name'})
	{
	    $dev = $self->rig->init_device('relay',$self->{'_output_relay_name'});
	    $dev->readstring_ignore(1) if ($dev);
	}
	push @hide_tags,'control_relay_type','control_relay_tty','control_relay_address','control_relay_channel' if ($dev);
	$self->die("No relay named '".$self->{'output_relay_name'}."' returned by rig ".$self->rig->name) if (!$dev && $self->{'output_relay_name'});
	my $devname = 'MFC_'.$self->name.'_control';
	$dev = $self->rig->get_device('relay',$devname) unless ($dev);
        unless ($dev)
	{
	    my $type = $self->get_conf('control_relay_type');
	    $type = 'ICP' unless ($type);
	    $self->{'_control_relay_type'} = $type;
	    my $tty = $self->get_conf('control_relay_tty');
	    my $addr = $self->get_conf('control_relay_address');
	    my $ch = $self->get_conf('control_relay_channel');
	    $dev = RFC::Device::new_RELAY($self->rig,$devname,$type,$tty,$addr,$ch);
	    $self->rig->register_device('relay',$dev);
	    $dev->readstring_ignore(1) if ($dev);
	}
	else
	{
	    $self->{'_control_relay_type'} = $dev->type;
	}
	$self->{'_control_relay'} = $dev;
	$dev->set_readonly() if ($dev);
    }
    else
    {
	push @hide_tags,'control_relay_name','control_relay_type','control_relay_tty','control_relay_address','control_relay_channel';
    }
    $self->{'_gas_change'} = $self->get_conf('gas_change');
    if ($self->{'_gas_change'} && $self->{'_gas_change'} =~ /auto/i)
    {
	### Register Multiplexer from rig if present ###
	my $mult = $self->get_conf('gas_multiplexer');	
	$self->die("No multiplexer name specified for MFC ".$self->name()) unless ($mult);
	my $device = $self->rig->get_device('multiplex',$mult);
	unless ($device)
	{
	    $device = $self->rig->init_device('multiplex',$mult);
	    $device->readstring_ignore(1) if ($device);
	}
	if ($device)
	{
	    $self->{'_multiplexer'} = $device->attatch_controler($self);
	}
	else
	{
	    my $type = $self->rig->get_cv('gas_multiplexer_'.$self->name,'type');
	    $type = 'ICP' unless ($type);
	    my $dev = RFC::Device::new_Multiplexer($self->rig,$mult,$type,$self);
	    $self->die("No multiplexer found for MFC ".$self->name) unless ($dev);
	    $dev->readstring_ignore(1) if ($dev);
	    $self->{'_multiplexer'} = $dev;
	    $self->rig->register_device('multiplex',$dev);
	}
    }
    $self->{'_hide_tags'} = \@hide_tags;
    return $self;
}

sub maxflow()
{
    my $self = shift;
    my $cfactor = RFC::Main::get_cv('gas',$self->{'_calibrated_gas'});
    my $factor = RFC::Main::get_cv('gas',$self->{'_gas'});
    eval {
	my $d = $self->rig->get_device('gas',$self->{'_gas'});
	$d = $self->rig->init_device('gas',$self->{'_gas'}) unless ($d);
	$factor = RFC::Main::get_cv('gas',$d->gas());
    };
    if ($@)
    {
	my $str = $@;
	$self->writedebug($str);
    }
    if ($self->multiplexer)
    {
	### Check gas factor for selected gas if multiplexer has changed! ###
	my $dev = $self->multiplexer();
	$dev = $self->rig->multiplex($self->multiplexer) unless (ref($dev) =~ /RFC::Multiplex/);
	if ($dev)
	{
	    if ($dev->gas(@_))
	    {
		$factor = RFC::Main::get_cv('gas',$dev->gas(@_));
		unless ($factor)
		{
		    $self->writedebug("Warning, no gas factor was found for gas '".$dev->gas."' reported by multiplexer '".$dev->name."', Setting gas factor to default gas for controler: '".$self->{'_gas'}."'");
		    $factor = RFC::Main::get_cv('gas',$self->{'_gas'});
		}
	    }
	    else
	    {
		$self->writedebug("Warning, no gas was reported by multiplexer '".$dev->name."', Setting gas factor to default gas for controler: '".$self->{'_gas'}."'");
	    }
	}
    }
    ### Catch any divide by 0 error.. ###
    $self->die('No gas factor found for gas '.$self->{'_gas'}.'!') unless $factor;
    $self->die('No gas factor found for gas '.$self->{'_calibrated_gas'}.'!') unless $cfactor;
    return $self->{'_calibrated_maxflow'} * $factor / $cfactor;
}

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 $flow = $self->read($data);
    my $ignore = $self->{'_channel_device'}->readstring_ignore;
    $self->{'_channel_device'}->readstring_ignore(0);
    my $str = $self->{'_channel_device'}->readstring($data);
    $self->{'_channel_device'}->readstring_ignore($ignore);
    my @l = split(/\s+/,$str);
    my $res = join(' ',$self->name,$l[1],$l[2],$flow);    
    $self->writedebug($res) if ($self->debug > 1);
    return $res;
}

sub read_raw
{
    my $self = shift;
    my $volt = undef;
    my $flow = 0;
    my $data = undef;
    if (@_)
    {
        $data = $_[0];
    }
    else
    {
	$data = RFC::Cache->new();
	$data->debug($self->debug()) if ($self->debug);
    }
    $self->check_cache($data);
    $volt = $self->{'_channel_device'}->read($data);
    $self->writedebug("No data found for MFC ".$self->name.' for rig '.$self->rig->name.', assuming zero flow!') unless (defined($volt));
    if ($self->{'_range'} =~ /1-5V/i)
    {
	$flow = ($volt-1) / 4;
    }
    elsif ($self->{'_range'} =~ /0-5V/i)
    {
	$flow = $volt / 5;
    }
    elsif ($self->{'_range'} =~ /0-10V/i)
    {
	$flow = $volt / 10;
    }
    elsif ($self->{'_range'} =~ /2-10V/i)
    {
	$flow = ($volt-2) / 8;
    }
    elsif ($self->{'_range'} =~ /4-20mA/i)
    {
	$flow = ($volt-4) / 16;
    }
    elsif ($self->{'_range'} =~ /0-20mA/i)
    {
	$flow = $volt / 20;
    }
    else
    {
	$self->die("Unknown range ".$self->{'_range'});
    }
    $self->writedebug("Flow for ".$self->name." was $flow") if ($self->debug > 1);
    $flow = $flow * $self->maxflow; 
    if ($self->{'_spline'} && defined($flow) && $flow =~ /([\d\.\-\+eE]+)/)
    {
        $flow = $1+0;
        ### Do spline conversion here ###
        $self->{'_raw_flow'} = $flow;
        my @list = split(/\s*\,\s*/,$self->{'_spline'});
	my $spline = RFC::Spline->new(join("\n",@list));
	$spline->debug($self->debug);
	my $res = $spline->value($flow);
        $self->writedebug("Result: '$res'") if ($self->debug);
        $flow = $res;
        $self->{'_corrected_flow'} = $flow;
    }
    my $str = time .' MFC_'.$self->name.' '.$flow;
    $self->rig->kanaldata($str) unless ($self->readstring_ignore);
    $flow = $flow * $self->{'_read_factor'};
    return $flow;
}

sub read_nocache
{
    my $self = shift;
    my $flow = $self->read_raw(@_);
    if ($self->{_multiplexer})
    {
	my $mgas = $self->{'_multiplexer'}->gas_name(@_);
	unless ($mgas && $mgas eq $self->{'_gas'})
	{
	    my $str = "Warning, Gas multiplexer not engaged as expected (wrong gas or no gas selected), gas flow possibly wrong! Setting flow to 0!";
	    $self->writedebug($str);
	    $flow = 0;
	}
    }
    $self->set_cache($flow);
    $self->{'_last_read'} = Time::HiRes::time;
    return $flow;
}

sub get_output_value
{
    my $self = shift;
    my $flow = shift;
    my $volt = 0;
    if ($self->{'_range'} =~ /1-5V/i)
    {
	$volt = $flow * 4 / $self->maxflow + 1;
    }
    elsif ($self->{'_range'} =~ /0-5V/i)
    {
	$volt = $flow * 5 / $self->maxflow;
    }
    elsif ($self->{'_range'} =~ /0-10V/i)
    {
	$volt = $flow * 10 / $self->maxflow;
    }
    elsif ($self->{'_range'} =~ /2-10V/i)
    {
	$volt = $flow * 8 / $self->maxflow + 2;
    }
    elsif ($self->{'_range'} =~ /4-20mA/i)
    {
	$volt = $flow * 16 / $self->maxflow + 4;
    }
    elsif ($self->{'_range'} =~ /0-20mA/i)
    {
	$volt = $flow * 20 / $self->maxflow;
    }
    else
    {
	$self->die("Unknown range ".$self->{'_range'});
    }
    return $volt;
}

sub setflow($)
{
    my $self = shift;
    my $flow = shift;
    $flow = $flow * $self->{'_setflow_factor'};
    $self->init_output() unless ($self->{'_output_device'});
    $flow = $self->maxflow if ($flow > $self->maxflow);
    my $gdev = $self->rig->get_device('gas',$self->{'_gas'});
    my $cutoff = $gdev->cutoff_set;
    $cutoff = 0 unless (defined($cutoff) && $cutoff > 0);
    $self->writedebug("Cutoff is $cutoff L/hour for gas ".$self->{'_gas'}) if ($self->debug > 3); 
    my $oldflow = $self->read;
    my $volt = 0;
    if ($flow < $cutoff)
    {
	$self->proglog("Gas flow for ".$self->{'_gas'}." is to be set below cutoff ($cutoff L/hour), setting 0 L/hour!",@_) unless ($flow == 0);
	$flow = 0;
	if ($self->{'_use_underrange_cutoff'} =~ /Yes/i)
	{
	    $volt = -1;
	    $volt = $self->{'_output_device'}->min() if ( $self->{'_output_device'}->min() > $volt);
	}
	else
	{
	    $volt = $self->get_output_value(0);
	}
    }
    else
    {
	$volt = $self->get_output_value($flow);
    }
    if ($self->{'_relay'})
    {
	if ($flow && $oldflow < $cutoff)
	{
	    $self->writedebug("Setting bypass relay for ".$self->{'_gas'}." to 1");
	    $self->{'_relay'}->set(1,@_);
	    sleep(1);
	    if ($self->{'_control_relay'})
	    {
		my $status = 1;
		$self->writedebug("Setting control relay for ".$self->{'_gas'}." to $status");
		$self->{'_control_relay'}->set($status,@_);
	    }	
	    sleep(1);
	    if ($self->{'_output_name'})
	    {
		$self->writedebug("Setting flow to $flow for gas ".$self->{'_gas'}." using analog MFC controlled by ".$self->{'_output_device'}->name);
	    }
	    else
	    {
		$self->writedebug("Setting flow to $flow for gas ".$self->{'_gas'}." using analog MFC controlled on channel ".$self->{'_control_channel'}." on tty ".$self->{'_tty'}.", address ". $self->{'_address'});
	    }
	    my $res = $self->{'_output_device'}->set($volt,@_);
	    $self->writedebug($res) if ($self->debug > 1);
	    sleep($self->{'_relay_time'}-1);
	    $self->writedebug("Setting bypass relay for ".$self->{'_gas'}." to 0");
	    $self->{'_relay'}->set(0,@_);
	}
    }
    my $status = 0;
    $status = 1 if ($flow > 0);
    if ($self->{'_control_relay'} && $self->{'_control_relay'}->read() != $status)
    {
	$self->writedebug("Setting control relay for ".$self->{'_gas'}." to $status");
	$self->{'_control_relay'}->set($status,@_);
    }
    if ($self->{'_output_name'})
    {
	$self->writedebug("Setting flow to $flow for gas ".$self->{'_gas'}." using analog MFC controlled by ".$self->{'_output_device'}->name);
    }
    else
    {
	$self->writedebug("Setting flow to $flow for gas ".$self->{'_gas'}." using analog MFC controlled on channel ".$self->{'_control_channel'}." on tty ".$self->{'_tty'}.", address ". $self->{'_address'});
    }
    my $res = $self->{'_output_device'}->set($volt,@_);
    $self->writedebug($res) if ($self->debug > 1);
    $self->proglog("Flow for gas ".$self->{'_gas'}." set to $flow L/hour",@_);
    $self->notify('setflow',$flow,@_);
    return $flow;
}

sub print_config()
{
    my $self = shift;
    $self->init_output() unless defined($self->{'_output_device'});
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'MFC type: '.$self->type;
    push @arr,'Mode: '.$self->mode;
    push @arr,$self->{'_output_device'}->print_config;
    push @arr,$self->{'_channel_device'}->print_config;
    if ($self->{'_relay'})
    {
	push @arr,$self->{'_relay'}->print_config;
	push @arr,'Relay_time: '.$self->{'_relay_time'};
    }
    if ($self->{'_control_relay'})
    {
	push @arr,$self->{'_control_relay'}->print_config;
    }
    if ($self->{'_multiplexer'})
    {
	push @arr,$self->{'_multiplexer'}->print_config;
    }
    return join (', ',@arr);
}

package RFC::AnalogReadonly;
our @ISA = qw(RFC::Analog);

sub new()
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'_mode'} = 'Readonly';
    $self->{'_readonly'} = 1;
    $self->{'_type'} = 'AnalogReadonly';
    $self->{'_channel'} = undef;
    $self->{'_channel_device'} = undef;
    $self->{'_calibrated_gas'} = undef;
    $self->{'_calibrated_maxflow'} = 1;
    $self->{'_gasses'} = ['n2'];
    $self->{'_gas'} = 'n2';
    $self->{'_relay'} = undef;
    $self->{'_relay_type'} = undef;
    $self->{'_relay_time'} = 0;
    $self->{'_control_relay'} = undef;
    $self->{'_control_relay_type'} = undef;
    $self->{'_gas_change'} = 'manual';
    $self->{'_multiplexer'} = undef;
    $self->{'_values_mode'} = ['Readonly'];
    $self->{'_values_output_range'} = ['0-5V','1-5V'];
    $self->{'_setup_tags'} = ['mode','channel_name','calibrated_gas','calibrated_maxflow','accuracy','output_range','gas','gas_change','gas_multiplexer','gasses','read_factor','title','show_plot','use_cache'];
    $self->{'_legacy_tags'} = [];
    $self->{'_default'} = {
	'mode' => 'Readonly',
	'callibrated_gas' => 'n2',
	'gas' => 'n2',
	'controler_mode' => 'Flowrate',
	'gasses' => 'n2',
	'gas_change' => 'Manual',
	'relay' => 'NO',
	'relay_name' => '',
	'relay_tty' => 'ttyS0',
	'relay_address' => 1,
	'relay_channel' => 0,
	'relay_time' => 0,
	'control_relay' => 'NO',
	'control_relay_name' => '',
	'control_relay_tty' => 'ttyS0',
	'control_relay_address' => 1,
	'control_relay_channel' => 0,
	'control_relay_type' => 'ICP',
	'output_range' => '0-5V',
	'channel_type' => 'Keithley',
	'gas' => 'n2',
	'calibrated_gas' => 'n2',
	'calibrated_maxflow' => 100,
	'channel' => '1:101',
	'channel_type' => 'Keithley',
	'channel_tty' => 'ttyS0',
	'channel_address' => 1,
	'channel_name' => '',
        'show_plot'=>'Yes',
	'spline' => '',
	'read_factor' => 1,
        'use_cache'=>'No',
    };
    bless $self,$class;
    $self->init;
    $self->{'_controler_mode'} = 'Flowrate';
    return $self;
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'MFC type: '.$self->type;
    push @arr,'Mode: '.$self->mode;
    push @arr,$self->{'_channel_device'}->print_config;
    if ($self->{'_relay'})
    {
	push @arr,$self->{'_relay'}->print_config;
	push @arr,'Relay_time: '.$self->{'_relay_time'};
    }
    if ($self->{'_control_relay'})
    {
	push @arr,$self->{'_control_relay'}->print_config;
    }
    if ($self->{'_multiplexer'})
    {
	push @arr,$self->{'_multiplexer'}->print_config;
    }
    return join (', ',@arr);
}

sub test()
{
    my $self = shift;
    return 0 unless($self->{'_channel_device'}->test());
    if ($self->{'_relay'})
    {
	return 0 unless($self->{'_relay'}->test());
    }
    if ($self->{'_control_relay'})
    {
	return 0 unless($self->{'_control_relay'}->test());
    }
    if ($self->{'_multiplexer'})
    {
	return 0 unless($self->{'_multiplexer'}->test());
    }
    return 1;
}

sub Accept
{
    my $self = shift;
    my $visitor= shift;
    return undef if ($self->{'_seen_visitors'}{$visitor});
    $self->init_output() unless ($self->{'_output_device'});
    $self->{'_channel_device'}->Accept($visitor);
    $self->{'_relay'}->Accept($visitor) if (exists($self->{'_relay'}) && $self->{'_relay'});
    $self->{'_control_relay'}->Accept($visitor) if (exists($self->{'_control_relay'}) && $self->{'_control_relay'});
    $self->{'_multiplexer'}->Accept($visitor) if (exists($self->{'_multiplexer'}) && $self->{'_multiplexer'});
    $visitor->VisitComplex($self);
    $self->{'_seen_visitors'}{$visitor} = 1;
    return undef;
}


sub test_string()
{
    my $self = shift;
    my $flow = $self->read(@_);
    my $string = "Flow for controler ".$self->name." was $flow L/hour\n";
    $string .= 'Raw measured flow before spline correction: '.$self->{'_raw_flow'}. ', after correction: '.$self->{'_corrected_flow'}."\n" if ($self->{'_spline'});
    $string .= 'Channel device: '.$self->{'_channel_device'}->test_string(@_)."\n";
    $string .= $self->{'_relay'}->test_string(@_)."\n" if (exists($self->{'_relay'}) && $self->{'_relay'});
    $string .= $self->{'_control_relay'}->test_string(@_)."\n" if (exists($self->{'_control_relay'}) && $self->{'_control_relay'});
    $string .= $self->{'_multiplexer'}->test_string(@_)."\n" if (exists($self->{'_multiplexer'}) && $self->{'_multiplexer'});
    return $string;
}

sub setflow($)
{
    my $self = shift;
    my $flow = shift;
    $self->errorlog("Warning: Device ".$self->name." is a read only device and thus can not set flow!",@_);
    return $flow;
}

sub init()
{
    my $self = shift;
    my @hide_tags;
    foreach my $tag ('spline','gas','calibrated_gas','calibrated_maxflow','output_range','read_factor','controler_mode','accuracy')
    {
	$self->{'_'.$tag} = $self->get_default($tag);
        my $conf = $self->get_conf($tag);
        $self->{'_'.$tag} = $conf if (defined($conf) && $conf =~ /\w/);
    }
    if ($self->{'_spline'})
    {
        my @list = split(/\s*\,\s*/,$self->{'_spline'});
	$self->{'_info_string'} = 'This device still uses the deprecated spline interpolation!';
        $self->die("Spline interpolation tables must have at least 2 lines, only one detected!") unless (@list > 1);
    }
    $self->{'_accuracy'} = $self->{'_accuracy'} / 100 if (defined($self->{'_accuracy'}));
    $self->{'_range'} = $self->{'_output_range'};
    my $type = $self->get_conf('channel_type');
    $type = 'Keithley' unless ($type);
    push @hide_tags,'channel','channel_type','channel_tty','channel_address' if ($self->get_conf('channel_name'));
    my $dev = $self->rig->get_device('simplechannel',$self->rig->get_cv('controler_'.$self->name,'channel_name'));
    unless ($dev || !$self->get_conf('channel_name'))
    {
	$dev = $self->rig->init_device('simplechannel',$self->rig->get_cv('controler_'.$self->name,'channel_name'));
	$dev->readstring_ignore(1) if ($dev);
    }
    $self->die("No simplechannel device named '".$self->get_conf('channel_name')."' returned by rig ".$self->rig->name) if (!$dev && $self->get_conf('channel_name'));
    my $devname = 'MFC_'.$self->name.'_input';
    $dev = $self->rig->get_device('simplechannel',$devname) unless ($dev);
    unless ($dev || !$self->get_conf('channel_name'))
    {
	$dev = $self->rig->init_device('simplechannel',$self->rig->get_cv('controler_'.$self->name,'channel_name'));
	$dev->readstring_ignore(1) if ($dev);
    }
    unless ($dev)
    {
	$self->{'_channel'} = $self->get_conf('channel');
	if ($type !~ /Keithley/i)
	{
	    my $tty = $self->get_conf('channel_tty');
	    my $addr = $self->get_conf('channel_address');
	    $dev = RFC::Device::new_SimpleChannel($self->rig,$devname,$type,$tty,$addr,$self->{'_channel'});
	}
	else
	{
	    $dev = RFC::Device::new_SimpleChannel($self->rig,$devname,$type,$self->{'_channel'});
	}
	$dev->readstring_ignore(1);
	$self->rig->register_device('simplechannel',$dev);
    }
    $self->{'_channel_device'} = $dev;
    my $gasstr = $self->get_conf('gasses');
    unless ($gasstr)
    {
	$gasstr = $self->{'_gas'}; 
	$self->{'_default'}{'gasses'} = $gasstr;
    }
    my @gasses = split(/\s*\,\s*/,$gasstr);
    $self->{'_gasses'} = \@gasses;
    my $relay = $self->get_conf('relay');
    if ($relay && $relay =~ /yes/i)
    {
	$self->{'_relay_name'} = $self->get_conf('relay_name');
	my $dev = $self->rig->get_device('relay',$self->{'_relay_name'});
	unless ($dev || !$self->{'_relay_name'})
	{
	    $dev = $self->rig->init_device('relay',$self->{'_relay_name'});
	    $dev->readstring_ignore(1) if ($dev);
	}
	push @hide_tags,'relay_type','relay_tty','relay_address','relay_channel' if ($dev);
	$self->die("No relay named '".$self->{'_relay_name'}."' returned by rig ".$self->rig->name) if (!$dev && $self->{'_relay_name'});
	my $devname = 'MFC_'.$self->name.'_bypass';
	$dev = $self->rig->get_device('relay',$devname) unless ($dev);
	unless ($dev)
	{
	    my $type = $self->get_conf('relay_type');
	    $type = 'ICP' unless ($type);
	    $self->{'_relay_type'} = $type;
	    my $tty = $self->get_conf('relay_tty');
	    my $addr = $self->get_conf('relay_address');
	    my $ch = $self->get_conf('relay_channel');
	    $dev = RFC::Device::new_RELAY($self->rig,$devname,$type,$tty,$addr,$ch);
	    $self->rig->register_device('relay',$dev);
	    $dev->readstring_ignore(1) if ($dev);
	}
	else
	{
	    $self->{'_relay_type'} = $dev->type;
	}
	$self->{'_relay_time'} = $self->get_conf('relay_time');
	$self->{'_relay'} = $dev;
    }
    else
    {
        push @hide_tags,'relay_name','relay_type','relay_tty','relay_address','relay_channel','relay_time';
    }
    $relay = $self->get_conf('output_control_relay');
    if ($relay && $relay =~ /yes/i)
    {
	$self->{'_output_relay_name'} = $self->get_conf('control_relay_name');
	my $dev = $self->rig->get_device('relay',$self->{'_output_relay_name'});
	unless ($dev || !$self->{'_output_relay_name'})
	{
	    $dev = $self->rig->init_device('relay',$self->{'_output_relay_name'});
	    $dev->readstring_ignore(1) if ($dev);
	}
	push @hide_tags,'control_relay_type','control_relay_tty','control_relay_address','control_relay_channel' if ($dev);
	$self->die("No relay named '".$self->{'_output_relay_name'}."' returned by rig ".$self->rig->name) if (!$dev && $self->{'_output_relay_name'});
	my $devname = 'MFC_'.$self->name.'_control';
	$dev = $self->rig->get_device('relay',$devname) unless ($dev);
        unless ($dev)
	{
	    my $type = $self->get_conf('control_relay_type');
	    $type = 'ICP' unless ($type);
	    $self->{'_control_relay_type'} = $type;
	    my $tty = $self->get_conf('control_relay_tty');
	    my $addr = $self->get_conf('control_relay_address');
	    my $ch = $self->get_conf('control_relay_channel');
	    $dev = RFC::Device::new_RELAY($self->rig,$devname,$type,$tty,$addr,$ch);
	    $self->rig->register_device('relay',$dev);
	    $dev->readstring_ignore(1) if ($dev);
	}
	else
	{
	    $self->{'_control_relay_type'} = $dev->type;
	}
	$self->{'_control_relay'} = $dev;
    }
    else
    {
        push @hide_tags,'control_relay_name','control_relay_type','control_relay_tty','control_relay_address','control_relay_channel';
    }
    $self->{'_gas_change'} = $self->get_conf('gas_change');
    if ($self->{'_gas_change'} && $self->{'_gas_change'} =~ /auto/i)
    {
	### Register Multiplexer from rig if present ###
	my $mult = $self->get_conf('gas_multiplexer');	
	$self->die("No multiplexer name specified for MFC ".$self->name()) unless ($mult);
	my $device = $self->rig->get_device('multiplex',$mult);
	unless ($device)
	{
	    $device = $self->rig->init_device('multiplex',$mult);
	    $device->readstring_ignore(1) if ($device);
	}
	if ($device)
	{
	    $self->{'_multiplexer'} = $device->attatch_controler($self);
	}
	else
	{
	    my $type = $self->rig->get_cv('gas_multiplexer_'.$self->name,'type');
	    $type = 'ICP' unless ($type);
	    my $dev = RFC::Device::new_Multiplexer($self->rig,$mult,$type,$self);
	    $self->die("No multiplexer found for MFC ".$self->name) unless ($dev);
	    $dev->readstring_ignore(1) if ($dev);
	    $self->{'_multiplexer'} = $dev;
	    $self->rig->register_device('multiplex',$dev);
	}
    }
    $self->{'_hide_tags'} = \@hide_tags;
    return $self;
}


package RFC::Brooks;
our @ISA = qw(RFC::MFC);

sub new()
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'_mode'} = 'Automatic';
    $self->{'_type'} = 'Brooks';
    $self->{'_tty'} = 'ttyS0';
    $self->{'_channel'} = undef;
    $self->{'_calibrated_gas'} = undef;
    $self->{'_calibrated_maxflow'} = 100;
    $self->{'_unit'} = 'L/hour';
    $self->{'_gasses'} = ['n2'];
    $self->{'_gas'} = 'n2';
    $self->{'_relay'} = undef;
    $self->{'_relay_type'} = undef;
    $self->{'_relay_time'} = 0;
    $self->{'_control_relay'} = undef;
    $self->{'_control_relay_type'} = undef;
    $self->{'_gas_change'} = 'manual';
    $self->{'_multiplexer'} = undef;
    $self->{'_values_mode'} = ['Automatic'];
    $self->{'_values_unit'} = ['L/s','L/min','L/hour','mL/s','mL/min','mL/hour','m3/s','m3/min','m3/hour'];
    $self->{'_setup_tags'} = ['mode','tty','channel','calibrated_gas','calibrated_maxflow','unit','gas','relay','relay_time','relay_name','output_control_relay','control_relay_name','gas_change','gas_multiplexer','gasses','setflow_factor','read_factor','title','show_plot','use_cache','persistent_settings'];
    $self->{'_legacy_tags'} = ['mode','tty','channel','calibrated_gas','calibrated_maxflow','unit','gas','relay','relay_time','relay_name','relay_type','relay_tty','relay_address','relay_channel','output_control_relay','control_relay_name','control_relay_type','control_relay_tty','control_relay_address','control_relay_channel','gas_change','gas_multiplexer','gasses','setflow_factor','read_factor','title','show_plot','use_cache','persistent_settings'];
    $self->{'_desc'} = {
	'mode' => 'Device control mode',
	'controler_mode' => 'Controler mode of operation',
	'tty' => 'Communication device (ex. ttyS0)',
	'channel' => 'Tag number (ex 05691001), NB must be 8 digits!',
	'calibrated_gas' => 'The gas for which the maxflow is specified on the controler (usually nitrogen)',
	'calibrated_maxflow' => 'Maximum flowrate for the callibrated gas (Note in L/hour!)',
	'unit' => 'Flow unit in which the controler reports the gas flow. MB This may be different than the unit the maxflow is given in!',
	'gas' => 'selected gas',
	'gas_change' => 'Gas change mode',
	'gas_multiplexer' => 'Name of gas multiplexer device if any',
	'gasses' => 'Avaliable gasses, Must contain a comma separated list of gas names (device name!) that the MFC can be used to control (in case of no multiplexer, just the single gas connected).',
	'title' => 'Optional title',
	'relay' => 'Bypass relay avaliable',
	'relay_name' => 'Bypass relay name',
	'relay_tty' => 'Bypass relay communication device (used only if no relay name is specified)',
	'relay_address'=> 'Bypass relay address (used only if no relay name is specified)',
	'relay_channel' => 'bypass relay channel (used only if no relay name is specified)',
	'relay_time' => 'bypass relay engage time (seconds)',
	'relay_type' => 'bypass relay device type (used only if no relay name is specified)',
	'control_relay_type' => 'Control relay device type (used only if no control relay name is specified)',
	'output_control_relay' => 'Switch controling if an external output control device is fitted (for instance a magnetic valve for forcing complete cutoff of gas)',
	'control_relay' => 'Cutoff relay avaliable',
	'control_relay_name' => 'Cutoff relay name',
	'control_relay_address' => 'Control relay address (used only if no control relay name is specified)',
	'control_relay_channel' => 'Control relay channel (used only if no control relay name is specified)',
	'control_relay_tty' => 'Control relay tty (used only if no control relay name is specified)',
	'spline' => 'Spline interpolation table for correction according to callibration sheet if any (leave blank if no callibration)',
        'setflow_factor' => 'Callibration factor for settig flow',
        'read_factor' => 'Callibration factor for reading flow',
        'use_cache'=>'Determines if persisten caching of read values is allowed',
        'persistent_settings'=>'Determines if the device should be queried each time for flow trange settings or if settings should be cached on disk. Default for Brooks MFCs are Yes as communication overhead usually becomes a problem (a single MFC require 1 second for initialisation otherwise)',
    };
    $self->{'_default'} = {
	'mode' => 'Automatic',
	'controler_mode' => 'Flowrate',
	'channel' => '00000000',
	'tty' => 'ttyS0',
	'calibrated_gas' => 'n2',
	'calibrated_maxflow' => 100,
	'gas' => 'n2',
	'gasses' => 'n2',
	'gas_change' => 'Manual',
	'relay' => 'NO',
	'relay_type' => 'ICP',
	'relay_name' => '',
	'relay_tty' => 'ttyS0',
	'relay_address' => 1,
	'relay_channel' => 0,
	'relay_time' => 0,
	'control_relay' => 'NO',
	'control_relay_name' => '',
	'control_relay_tty' => 'ttyS0',
	'control_relay_address' => 1,
	'control_relay_channel' => 0,
	'unit' => 'L/hour',
	'output_control_relay' => 'NO',
	'control_relay_type' => 'ICP',
        'show_plot'=>'Yes',
	'spline' => '',
        'setflow_factor' => 1,
        'read_factor' => 1,
        'use_cache'=>'No',
        'persistent_settings'=>'Yes',
    };
    bless $self,$class;
    $self->{'_values_controler_mode'} = ['Flowrate'];
    $self->{'_ui_fn_names'} = ['query_settings'];
    $self->{'_ui_fn_desc'} = {
        'query_settings' => 'Get and store the settings which can be queried from the controler',
    };
    $self->init;
    $self->{'_setup_tags_readonly'} = ['calibrated_maxflow','unit','calibrated_gas'] if ($self->is_comm_ver(9,1,44));
    $self->{'_controler_mode'} = 'Flowrate';
    return $self;
}


sub readonly_value($)
{
    my $self = shift;
    my $tag = shift;
    return undef unless ($tag);
    return $self->{'_calibrated_maxflow'} if ($tag eq 'calibrated_maxflow');
    return $self->{'_calibrated_gas'} if ($tag eq 'calibrated_gas');
    return $self->{'_unit'} if ($tag eq 'unit');
    return undef;
}


sub comm
{
    my $self = shift;
    my $str = SocketClient::serial_client_raw($self->{'_tty'},'brooks',$self->{'_channel'},@_);
    $str = SocketClient::serial_client_raw($self->{'_tty'},'brooks',$self->{'_channel'},@_) unless(defined($str));
    return $str;
}

sub get_unit
{
    my $self = shift;
    my $str = $self->comm('query',1);
    return undef unless ($str);
    my @l = split(/[\s\t]+/,$str);
    return  $self->unit_code($l[2]);
}

sub get_cal_gas
{
    my $self = shift;
    my $str = $self->comm('query',150,1);
    return undef unless ($str);
    my @l = split(/[\s\t]+/,$str);
    my $gcode = $l[2];
    if ($l[2] != 1)
    {
	$gcode = $l[2];
	$str = $self->comm('query',150,$gcode);
	$str = '' unless ($str);
	@l = split(/[\s\t]+/,$str);	
    }
    my $gas = '';
    foreach my $c (@l[3..$#l])
    {
	my $char = chr($c);
	### Skip rest if non-alpha char is found (older Brooks controlers do not just pad string with spaces...) ###
	last unless ($char =~ /[\w\s]/);
	$gas .= $char;
    }
    ## Get rid of traling spaces ###
    $gas =~ s/\s+$//;
    return  lc($gas);
}


sub get_maxflow
{
    my $self = shift;
    my $str = $self->comm('query',151,1);
    return undef unless ($str);
    my @l = split(/[\s\t]+/,$str);
    my $gcode = 1;
    if ($l[2] != 1)
    {
	$gcode = $l[2];
	$str = $self->comm('query',151,$gcode);
	$str = '' unless ($str);
	@l = split(/[\s\t]+/,$str);
    }
    my $u = $self->unit_code($l[18]);
    my $flow = $self->IEEE_to_float(@l[19..22]);
    ### To do: Fix range if other than L/hour reported ###
    return $flow * 3600 / 1000 if ($u =~ /mL\/s/i);
    return $flow * 60 / 1000 if ($u =~ /mL\/m/i);
    return $flow / 1000 if ($u =~ /mL\/h/i);
    return $flow * 3600 if ($u =~ /L\/s/i);
    return $flow * 60 if ($u =~ /L\/m/i);
    return $flow if ($u =~ /L\/h/i);
    return $flow * 1000 * 3600 if ($u =~ /m3\/s/i);
    return $flow * 1000 * 60 if ($u =~ /m3\/m/i);
    return $flow * 1000 if ($u =~ /m3\/h/i);
}

sub query_settings
{
    my $self = shift;
    my @list = ();
    $self->rig->begin();
    eval {
	### Get unit ###
	my $unit = $self->get_unit();
	CORE::die("Could not get flow unit from controler!\n") unless ($unit);
	$self->change_cv('unit',$unit);
	push @list,'Unit: '.$unit;
	### Get calibrated gas ###
	my $gas = $self->get_cal_gas();
	CORE::die("Could not get calibrated gas from controler!\n") unless ($gas);
	$self->change_cv('calibrated_gas',$gas);
	push @list,'Calibrated gas: '.$gas;
	### Get maxflow ###
	my $range = $self->get_maxflow();
	CORE::die("Could not get calibrated maxflow from controler!\n") unless ($range);
	$self->change_cv('calibrated_maxflow',$range);
	push @list,'Calibrated maxflow: '.$range.' L/hour';
	my $gas_ok = 0;
	my @l = RFC::Main::get_gasses();
	foreach my $g (@l)
	{
	    next unless ($g eq $gas);
	    $gas_ok = 1;
	    last;
	}
	CORE::die("Gas $gas does not match any of the known gasses (".join(',',@list).")!\n") unless ($gas_ok);
        $self->rig->commit();
	push @list,'RELOAD';
    };
    if ($@)
    {
        $self->rig->rollback();
        push @list,'Error: '.$@;
    }
    return join("\n",@list);
}

sub float_to_IEEE
{
    ### Converts a number to IEEE-754 in reverse byte order ###
    my $self = shift;
    my $val = shift;
    my @res = ();
    foreach my $c (split(//,scalar reverse pack("f",$val)))
    {
	push @res,ord($c);
    }
    return @res;
}

sub IEEE_to_float
{
    ### Converts 4 numbers (representing bytes, range 0 to 255)         ###
    ### to floating point acording to IEEE-754 with reverse byte order  ###
    ### 
    my $self = shift;
    my @l = @_;
    return -1 unless (@l == 4);
    my $st = '';
    foreach my $c (@l)
    {
	$st .= chr($c);
    }
    return sprintf("%.3g",unpack("f",scalar reverse $st));
}

sub test()
{
    my $self = shift;
    my $string = $self->comm('init');
    return 0 unless (defined($string) && $string =~ /\d+/);
    my @l = split(/[\s\t]+/,$string);
    return 0 unless (@l > 2);
    my $ok = 0;
    foreach (@l)
    {
	$ok = 1 if $_;
    }
    return 0 unless $ok;
    if ($self->{'_relay'})
    {
	return 0 unless($self->{'_relay'}->test());
    }
    if ($self->{'_control_relay'})
    {
	return 0 unless($self->{'_control_relay'}->test());
    }
    if ($self->{'_multiplexer'})
    {
	return 0 unless($self->{'_multiplexer'}->test());
    }
    return 1;
}

sub test_string()
{
    my $self = shift;
    my $string = $self->comm('init');
    $string = '' unless defined($string);
    my @l = split(/[\s\t]+/,$string);
    my $ok = 0;
    foreach (@l)
    {
	$ok = 1 if $_;
    }
    chomp $string;
    $ok = 0 unless (@l > 2);
    return "Init failed for MFC ".$self->name().". Returned string: '$string'\n" unless ($ok);
    my $flow = $self->read(@_);
    $string .= "Flow for controler ".$self->name." was $flow L/hour\n";
    $string .= 'Raw measured flow before spline correction: '.$self->{'_raw_flow'}. ', after correction: '.$self->{'_corrected_flow'}."\n" if ($self->{'_spline'});
    $string .= $self->{'_relay'}->test_string(@_)."\n" if (exists($self->{'_relay'}) && $self->{'_relay'});
    $string .= $self->{'_control_relay'}->test_string(@_)."\n" if (exists($self->{'_control_relay'}) && $self->{'_control_relay'});
    $string .= $self->{'_multiplexer'}->test_string(@_)."\n" if (exists($self->{'_multiplexer'}) && $self->{'_multiplexer'});
    return $string;
}


sub is_comm_ver
{
    my $self = shift;
    my @l = @_;
    ### Used to check if serial server is high enough version for intended operation ###
    ### Actual version value from server cached at device initialisation             ###
    return 0 if ($l[0] > $self->{'_comm_ver'}[0]);
    return 1 if ($l[0] < $self->{'_comm_ver'}[0]);
    return 0 if ($l[1] > $self->{'_comm_ver'}[1]);
    return 1 if ($l[1] < $self->{'_comm_ver'}[1]);
    return 0 if ($l[2] > $self->{'_comm_ver'}[2]);
    return 1;
}

sub init()
{
    my $self = shift;
    my @hide_tags;
    ### Skip if called from base class (tags not set correctly
    return $self unless ($self =~ /brooks/i);
    $self->load_conf();
    if ($self->{'_spline'})
    {
        my @list = split(/\s*\,\s*/,$self->{'_spline'});
	$self->{'_info_string'} = 'This device still uses the deprecated spline interpolation!';
        $self->die("Spline interpolation tables must have at least 2 lines, only one detected!") unless (@list > 1);
    }
    ### Get comminication version ###
    if (!$self->{'_has_ver'})
    {
	$self->{'_comm_ver'} = [0,0,0];
	if ($self->{'_tty'})
	{	
	    my $str = SocketClient::serial_client_raw($self->{'_tty'},'version');
	    if ($str && $str =~ /([\d\.]+)\s+build\s+(\d+)\.(\d+)/)
	    {
		$self->{'_comm_ver'} = [$1,$2,$3];
	    }
	    ### Only do it once ###
	    $self->{'_has_ver'} = 1;
	}
    }
    ### Only try to get settings communicate if tag is defined ### 
    my $tag = $self->{'_channel'};
    ### Only try if tag number is actualy set ###
    if (!$self->persistent && $self->is_comm_ver(9,1,44) && !$self->{'_has_init'} && $tag && ($tag+0))
    {
	### Read settings from device ###
	my $ok = 1;
	my $string = $self->comm('init');
	$ok = 0 unless (defined($string) && $string =~ /\d+/);
	if ($ok)
	{
	    my @l = split(/[\s\t]+/,$string);
	    $ok =  0 unless (@l > 2);
	}
	if ($ok)
	{
	    $self->{'_unit'} = $self->get_unit();
	    $self->{'_calibrated_maxflow'} = $self->get_maxflow();
	    my $gas = $self->get_cal_gas();
	    my $gas_ok = 0;
	    my @list = RFC::Main::get_gasses();
	    foreach my $g (@list)
	    {
		next unless ($g eq $gas);
		$gas_ok = 1;
		last;
	    }
	    ### Fallback if reported calibrated gas is unknown ###
	    $gas = $self->{'_calibrated_gas'} unless ($gas_ok);
	    $self->{'_calibrated_gas'} = $gas;
	}
	else
	{
	    my $str = "Could not get settings from MFC ".$self->name.'. Device not responding!';
	    $self->rig->warning_mail($self,$str);
	}
	### Only do it once ###
	$self->{'_has_init'} = 1;
    }
    my $gasstr = $self->get_conf('gasses');
    unless ($gasstr)
    {
	$gasstr = $self->{'_gas'}; 
	$self->{'_default'}{'gasses'} = $gasstr;
    }
    my @gasses = split(/\s*\,\s*/,$gasstr);
    $self->{'_gasses'} = \@gasses;
    my $relay = $self->get_conf('relay');
    if ($relay && $relay =~ /yes/i)
    {
	$self->{'_relay_name'} = $self->get_conf('relay_name');
	my $dev = $self->rig->get_device('relay',$self->{'_relay_name'});
	unless ($dev || !$self->{'_relay_name'})
	{
	    $dev = $self->rig->init_device('relay',$self->{'_relay_name'});
	    $dev->readstring_ignore(1) if ($dev);
	}
	push @hide_tags,'relay_type','relay_tty','relay_address','relay_channel' if ($dev);
	$self->die("No relay named '".$self->{'_relay_name'}."' returned by rig ".$self->rig->name) if (!$dev && $self->{'_relay_name'});
	my $devname = 'MFC_'.$self->name.'_bypass';
	$dev = $self->rig->get_device('relay',$devname) unless ($dev);
        unless ($dev)
	{
	    my $type = $self->get_conf('relay_type');
	    $type = 'ICP' unless ($type);
	    $self->{'_relay_type'} = $type;
	    my $tty = $self->get_conf('relay_tty');
	    my $addr = $self->get_conf('relay_address');
	    my $ch = $self->get_conf('relay_channel');
	    $dev = RFC::Device::new_RELAY($self->rig,$devname,$type,$tty,$addr,$ch);
	    $self->rig->register_device('relay',$dev);
	    $dev->readstring_ignore(1) if ($dev);
	}
	else
	{
	    $self->{'_relay_type'} = $dev->type;
	}
	$self->{'_relay_time'} = $self->get_conf('relay_time');
	$self->{'_relay'} = $dev;
    }
    else
    {
        push @hide_tags,'relay_name','relay_type','relay_tty','relay_address','relay_channel','relay_time';
	$self->{'_relay'} = undef;
    }
    $relay = $self->get_conf('output_control_relay');
    if ($relay && $relay =~ /yes/i)
    {
	$self->{'_output_relay_name'} = $self->get_conf('control_relay_name');
	my $dev = $self->rig->get_device('relay',$self->{'_output_relay_name'});
	unless ($dev || !$self->{'_output_relay_name'})
	{
	    $dev = $self->rig->init_device('relay',$self->{'_output_relay_name'});
	    $dev->readstring_ignore(1) if ($dev);
	}
	push @hide_tags,'control_relay_type','control_relay_tty','control_relay_address','control_relay_channel' if ($dev);
	$self->die("No relay named '".$self->{'_output_relay_name'}."' returned by rig ".$self->rig->name) if (!$dev && $self->{'_output_relay_name'});
	my $devname = 'MFC_'.$self->name.'_control';
	$dev = $self->rig->get_device('relay',$devname) unless ($dev);
        unless ($dev)
	{
	    my $type = $self->get_conf('control_relay_type');
	    $type = 'ICP' unless ($type);
	    $self->{'_control_relay_type'} = $type;
	    my $tty = $self->get_conf('control_relay_tty');
	    my $addr = $self->get_conf('control_relay_address');
	    my $ch = $self->get_conf('control_relay_channel');
	    $dev = RFC::Device::new_RELAY($self->rig,$devname,$type,$tty,$addr,$ch);
	    $self->rig->register_device('relay',$dev);
	    $dev->readstring_ignore(1) if ($dev);
	}
	else
	{
	    $self->{'_control_relay_type'} = $dev->type;
	}
	$self->{'_control_relay'} = $dev;
    }
    else
    {
	$self->{'_control_relay'} = undef;
	push @hide_tags,'control_relay_name','control_relay_type','control_relay_tty','control_relay_address','control_relay_channel';
    }
    $self->{'_gas_change'} = $self->get_conf('gas_change');
    if ($self->{'_gas_change'} && $self->{'_gas_change'} =~ /auto/i)
    {
	### Register Multiplexer from rig if present ###
	my $mult = $self->get_conf('gas_multiplexer');	
	$self->die("No multiplexer name specified for MFC ".$self->name()) unless ($mult);
	my $device = $self->rig->get_device('multiplex',$mult);
	unless ($device)
	{
	    $device = $self->rig->init_device('multiplex',$mult);
	    $device->readstring_ignore(1) if ($device);
	}
	if ($device)
	{
	    $self->{'_multiplexer'} = $device->attatch_controler($self);
	}
	else
	{
	    my $type = $self->rig->get_cv('gas_multiplexer_'.$self->name,'type');
	    $type = 'ICP' unless ($type);
	    my $dev = RFC::Device::new_Multiplexer($self->rig,$mult,$type,$self);
	    $self->die("No multiplexer found for MFC ".$self->name) unless ($dev);
	    $self->{'_multiplexer'} = $dev;
	    $dev->readstring_ignore(1) if ($dev);
	    $self->rig->register_device('multiplex',$dev);
	}
    }
    else
    {
	$self->{'_multiplexer'} = undef;
    }
    $self->{'_hide_tags'} = \@hide_tags;
    return $self;
}

sub maxflow()
{
    my $self = shift;
    my $cfactor = RFC::Main::get_cv('gas',$self->{'_calibrated_gas'});
    my $factor = RFC::Main::get_cv('gas',$self->{'_gas'});
    eval {
	my $d = $self->rig->init_device('gas',$self->{'_gas'});
	$factor = RFC::Main::get_cv('gas',$d->gas());
    };
    if ($@)
    {
	my $str = $@;
	$self->rig->warning_mail($self,$str);
	$self->writedebug($str);
    }
    ### Catch any divide by 0 error.. ###
    if ($self->multiplexer)
    {
	### Check gas factor for selected gas if multiplexer has changed! ###
	my $dev = $self->multiplexer();
	$dev = $self->rig->get_device('multiplex',$self->multiplexer) unless (ref($dev) =~ /RFC::Multiplex/);
	if ($dev)
	{
	    if ($dev->gas(@_))
	    {
		$factor = RFC::Main::get_cv('gas',$dev->gas(@_));
		unless ($factor)
		{
		    $self->writedebug("Warning, no gas factor was found for gas '".$dev->gas."' reported by multiplexer '".$dev->name."', Setting gas factor to default gas for controler: '".$self->{'_gas'}."'");
		    $factor = RFC::Main::get_cv('gas',$self->{'_gas'});
		}
	    }
	    else
	    {
		$self->writedebug("Warning, no gas was reported by multiplexer '".$dev->name."', Setting gas factor to default gas for controler: '".$self->{'_gas'}."'");
	    }
	}
    }
    $self->die('No gas factor found for gas '.$self->{'_gas'}.'!') unless $factor;
    $self->die('No gas factor found for gas '.$self->{'_calibrated_gas'}.'!') unless $cfactor;
    return $self->{'_calibrated_maxflow'} * $factor / $cfactor;
}

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 $flow = $self->read($data);
    my @t = $self->get_time();
    my $t = join(':',@t);
    return join(' ',$self->name,$t,time,$flow);
}

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

sub read_nocache
{
    my $self = shift;
    my $flow = $self->flow(@_);
    my $str = time.' MFC_'.$self->name.' '.$flow;
    $self->rig->kanaldata($str) unless ($self->readstring_ignore);
    if ($self->{_multiplexer})
    { 
	my $mgas = $self->{'_multiplexer'}->gas_name(@_);
	unless ($mgas && $mgas eq $self->{'_gas'})
	{
	    my $str = "Warning, Gas multiplexer not engaged as expected (wrong gas or no gas selected), gas flow possibly wrong! Setting flow to 0!";
	    $self->writedebug($str);
	    $flow = 0;
	}
    }
    $self->set_cache($flow);
    $self->{'_last_read'} = Time::HiRes::time;
    return $flow;
}

sub flow
{
    my $self = shift;
    my $ch = $self->{'_channel'};
    my $tty = $self->{'_tty'};
    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($tty,'brooks',$ch);
    my $flow = 0;
    if (defined($cached))
    {
	$self->writedebug("Using existing flow value for ".$self->name) if ($self->debug > 3);
	$flow = $cached;
    }
    else
    {

	$self->writedebug("Measuring flow for ".$self->name) if ($self->debug > 3);
	$flow = $self->comm('readflow');
	if (!defined($flow) || $flow =~ /Error/i)
	{
	    $flow = -1;
	}
	### Check for sanity ###
	if ($flow < 0)
	{
	    $self->writedebug("Flow for ".$self->name." was below 0, checking init and retrying") if ($self->debug > 3);
	    my $res1 = $self->comm('init');
	    chomp $res1 if (defined($res1));
	    select(undef,undef,undef,0.1);
	    my $res2 = $self->comm('init');
	    chomp $res2 if (defined($res2));
	    if (!defined($res1) || !defined($res2) || $res1 !~ /\d+/ || $res1 ne $res2)
	    {
		## For some reason init returns different values!!!! Log error ###
		$res1 = 'UNDEF' unless (defined($res1));
		$res2 = 'UNDEF' unless (defined($res2));
		my $str = "Warning: Init for Brooks controler ".$self->{'_channel'}." returned different values on successive calls ('$res1' and '$res2')!. Controler possibly corrupt!";
		$str = "Warning: Init failed for Brooks controler ".$self->{'_channel'}.", no reply recieved!" if ($res1 eq 'UNDEF' || $res2 eq 'UNDEF');
		$self->rig->warning_mail($self,$str);
		return -1;
	    }
	    else
	    {
		### Init works, try again ###
		$flow = $self->comm('readflow');
	    }
	}
	$self->writedebug("Result: $flow") if ($self->debug > 3);
	chomp $flow;
	if (defined($flow))
	{
	    $data->set($tty,'brooks',$ch,$flow);
	}
	else
	{
	    $self->writedebug('No flow returned by MFC '.$self->name.' for rig '.$self->rig->name.", assuming zero flow, String returned: '$flow'");
	    $flow = 0;
	}
    }
    $flow = $self->flow_to_L($flow);
    if ($self->{'_spline'} && defined($flow) && $flow =~ /([\d\.\-\+eE]+)/)
    {
	$flow = $1;
	### Do spline conversion here ###
	$self->{'_raw_flow'} = $flow;
        my @list = split(/\s*\,\s*/,$self->{'_spline'});
	my $spline = RFC::Spline->new(join("\n",@list));
	$spline->debug($self->debug);
	my $res = $spline->value($flow);
        $self->writedebug("Result: '$res'") if ($self->debug);
	$flow = $res;
	$self->{'_corrected_flow'} = $flow;
    }
    my $cfactor = RFC::Main::get_cv('gas',$self->{'_calibrated_gas'});
    my $factor = RFC::Main::get_cv('gas',$self->{'_gas'});
    eval {
	my $d = $self->rig->init_device('gas',$self->{'_gas'});
	$factor = RFC::Main::get_cv('gas',$d->gas());
    };
    if ($@)
    {
	my $str = $@;
	$self->rig->warning_mail($self,$str);
	$self->writedebug($str);
    }
    if ($self->multiplexer)
    {
	### Check gas factor for selected gas if multiplexer has changed! ###
	my $dev = $self->multiplexer();
	$dev = $self->rig->get_device('multiplex',$self->multiplexer) unless (ref($dev) =~ /RFC::Multiplex/);
	if ($dev)
	{
	    if ($dev->gas($data))
	    {
		$factor = RFC::Main::get_cv('gas',$dev->gas($data));
		unless ($factor)
		{
		    $self->writedebug("Warning, no gas factor was found for gas '".$dev->gas."' reported by multiplexer '".$dev->name."', Setting gas factor to default gas for controler: '".$self->{'_gas'}."'");
		}
	    }
	    else
	    {
		$self->writedebug("Warning, no gas was reported by multiplexer '".$dev->name."', Setting gas factor to default gas for controler: '".$self->{'_gas'}."'");
	    }
	    $factor = RFC::Main::get_cv('gas',$self->{'_gas'}) unless ($factor);
	}
    }
    ### Catch any divide by 0 error.. ###
    $self->die('No gas factor found for gas '.$self->{'_gas'}.'!') unless $factor;
    $self->die('No gas factor found for gas '.$self->{'_calibrated_gas'}.'!') unless $cfactor;
    $flow = $flow * $factor / $cfactor;
    $self->writedebug("Flow for ".$self->name." was $flow") if ($self->debug > 1);
    $flow = $flow * $self->{'_read_factor'};
    return $flow;
}

sub setflow()
{
    my $self = shift;
    my $flow = shift;
    $flow = $flow * $self->{'_setflow_factor'};
    $flow = $self->maxflow if ($flow > $self->maxflow);
    my $gdev = $self->rig->get_device('gas',$self->{'_gas'});
    my $cutoff = $gdev->cutoff_set;
    $cutoff = 0 unless (defined($cutoff) && $cutoff > 0);
    my $vo = 0;
    if ($flow < $cutoff)
    {
	$self->proglog("Gas flow for ".$self->{'_gas'}." is to be set below cutoff ($cutoff L/hour), setting 0 L/hour!",@_) unless ($flow == 0);
	$flow = 0;
	$vo = 2;
    }
    ### Calculate flow in percent ###
    my $lf = $flow * 100 / $self->maxflow;
    my $oldflow = $self->read;
    eval {
	if ($self->{'_relay'})
	{
	    if ($flow && $oldflow < $cutoff)
	    {
		$self->writedebug("Setting bypass relay for ".$self->{'_gas'}." to 1");
		$self->{'_relay'}->set(1,@_);
		sleep(1);
		if ($self->{'_control_relay'})
		{
		    my $status = 1;
		    $self->writedebug("Setting control relay for ".$self->{'_gas'}." to $status");
		    $self->{'_control_relay'}->set($status);
		}
		sleep(1);
		$self->writedebug("Setting flow to $flow for gas ".$self->{'_gas'}." using MFC ".$self->{'_channel'}." on tty ".$self->{'_tty'});
		my $res = $self->comm('override',$vo);
		chomp $res if (defined($res));
		CORE::die "Response from MFC did not match expected value, recieved $res, expected $vo\n" unless (defined($res) && $res !~ /error/i && $vo == $res);
		$res = $self->comm('setflow',$lf);
		chomp $res if (defined($res));
		CORE::die "Response from MFC did not match expected value, recieved $res, expected $lf\n" unless (defined($res) && $res !~ /error/i && (abs($lf - $res) < $self->maxflow / 100));
		sleep($self->{'_relay_time'}-1);
		$self->writedebug("Setting bypass relay for ".$self->{'_gas'}." to 0");
		$self->{'_relay'}->set(0,@_);
	    }
	}
	my $status = 0;
	$status = 1 if ($flow > 0);
	if ($self->{'_control_relay'} && $self->{'_control_relay'}->read() != $status)
	{
	    $self->writedebug("Setting control relay for ".$self->{'_gas'}." to $status");
	    $self->{'_control_relay'}->set($status,@_);
	}
	$self->writedebug("Setting flow to $flow for gas ".$self->{'_gas'}." using MFC ".$self->{'_channel'}." on tty ".$self->{'_tty'});
	my $res = $self->comm('override',$vo);
	chomp $res if (defined($res));
	CORE::die "Response from MFC did not match expected value, recieved $res, expected $vo\n" unless (defined($res) && $res !~ /error/i && $vo == $res);
	$res = $self->comm('setflow',$lf);
	chomp $res if (defined($res));
	$self->writedebug("Result of setflow: '$res'");
	CORE::die "Response from MFC did not match expected value, recieved $res, expected $lf\n" unless (defined($res) && $res !~ /error/i && (abs($lf - $res) < $self->maxflow / 100));
	CORE::die "Response from MFC did not match expected value, recieved $res, expected $lf\n" if ($res eq '-1');
	$self->rig->unset_warning($self);
    };
    if ($@)
    {
        my $str = "Error: Gas flow for controler ".$self->name." (".$self->{'_gas'}.") could not be set to $flow! Error: ".$@;
        $self->writedebug($str);
        $self->rig->errorlog($str,@_);
	return undef;
    }
    $self->proglog("Flow for gas ".$self->{'_gas'}." set to $flow L/hour",@_);
    $self->notify('setflow',$flow,@_);
    return $flow;
}

sub print_config()
{
    my $self = shift;
    my @arr = ();
    push @arr,'Name: '.$self->name;
    push @arr,'MFC type: '.$self->type;
    push @arr,'Mode: '.$self->mode;
    push @arr,'TTY: '.$self->{'_tty'};
    push @arr,'Tagnumber: '.$self->{'_channel'};
    if ($self->{'_relay'})
    {
	push @arr,$self->{'_relay'}->print_config;
	push @arr,'Relay_time: '.$self->{'_relay_time'};
    }
    if ($self->{'_control_relay'})
    {
	push @arr,$self->{'_control_relay'}->print_config;
    } 
    if ($self->{'_multiplexer'})
    {
	push @arr,$self->{'_multiplexer'}->print_config;
    }
    return join (', ',@arr);
}

sub unit_code
{
    my $self = shift;
    my $code = shift;
    return '' unless ($code);
    return 'L/min' if ($code == 17);
    return 'm3/hour' if ($code == 19);
    return 'L/s' if ($code == 24);
    return 'm3/s' if ($code == 28);
    return '%' if ($code == 57);
    return 'm3/min' if ($code == 131);
    return 'L/hour' if ($code == 138);
    return 'mL/s' if ($code == 170);
    return 'mL/min' if ($code == 171);
    return 'mL/hour' if ($code == 172);
    return 'Undefined';
}

sub flow_to_L
{
    my $self = shift;
    my $u = $self->{'_unit'};
    my $flow = shift;
    return $flow * 3600 / 1000 if ($u =~ /mL\/s/i);
    return $flow * 60 / 1000 if ($u =~ /mL\/m/i);
    return $flow / 1000 if ($u =~ /mL\/h/i);
    return $flow * 3600 if ($u =~ /L\/s/i);
    return $flow * 60 if ($u =~ /L\/m/i);
    return $flow if ($u =~ /L\/h/i);
    return $flow * 1000 * 3600 if ($u =~ /m3\/s/i);
    return $flow * 1000 * 60 if ($u =~ /m3\/m/i);
    return $flow * 1000 if ($u =~ /m3\/h/i);
}


package RFC::BrooksReadonly;
our @ISA = qw(RFC::Brooks);

sub new()
{
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'_readonly'} = 1;
    $self->{'_type'} = 'BrooksReadonly';
    $self->{'_setup_tags'} = ['mode','tty','channel','calibrated_gas','calibrated_maxflow','unit','controler_mode','gas','gas_change','gas_multiplexer','gasses','setflow_factor','read_factor','title','show_plot','use_cache','persistent_settings'];
    $self->{'_legacy_tags'} = [];
    $self->{'_relay'} = undef;
    $self->{'_control_relay'} = undef;
    bless $self,$class;
    return $self;
}



return 1;
