#!/usr/bin/perl

use utf8;
use strict;
use warnings;
use feature qw(switch);
use POSIX;
use Data::Dump qw(pp);

use Protocol::Modbus::PowerMod qw(modbus _MB_DEFAULT_PORT_ :FC :FUNC);
use Getopt::Long;

# Exit codes
use constant _OKAY_ => 0;
use constant _WARN_ => 1;
use constant _CRIT_ => 2;
use constant _UNKN_ => 3;

# Values organisation
use constant DATA  => 0;
use constant SCALE => 1;
use constant WARN  => 2;
use constant CRIT  => 3;
use constant DESC  => 4;
use constant INST  => 5;
use constant STAT  => 6;
use constant DEL   => 7;

use constant MIN => 0;
use constant MAX => 1;
use constant _NO_  => 0;
use constant _YES_ => 1;

my @default_bool;
$default_bool[WARN] = "x";
$default_bool[CRIT] = "1";

my @PREFIX_MESSAGE;
$PREFIX_MESSAGE[_OKAY_] = "Alle Werte im Normbereich.\n";
$PREFIX_MESSAGE[_WARN_] = "ACHTUNG: Folgende *Werte* sollten überprüft werden:\n";
$PREFIX_MESSAGE[_CRIT_] = "ACHTUNG: Folgende **Werte** außerhalb der Vorgaben:\n";
$PREFIX_MESSAGE[_UNKN_] = "ACHTUNG: Unbekannter Status.\n";


my @valid_types = ( "bool", "int", "uint", "int32", "uint32", "float", "quad" );
my @reg_type = ( "DATA", "SCALE", "WARN", "CRIT" );
my $_PREFIX_ = _YES_;

# Datastore
my ( @DATA, @addr, @type, @name );

# Yellow Subroutine
sub print_rcv_error_and_exit($$$)
{
    my ( $rcv_kind, $_count_, $_cnt_rcv_ ) = @_;
    print STDERR "UNKNOWN: Unterschied zwischen der Anzahl der geforderten ($_count_) und empfangenen ($_cnt_rcv_) Daten aus dem $rcv_kind-Register.\n";
    exit _UNKN_;
}


sub print_error($)
{
    my ( $_message_ ) = @_;

    print STDERR "UNKNOWN: Eine oder mehrere Optionen fehlerhaft.\n$_message_";
    exit _UNKN_;
}

sub print_help
{
    print <<__;

    $0 -h HOST  -u UNIT  -d DATA
                            [-c COUNT]          [-dt TYPE of DATA]       [-p PORT]
                            [-s SCALEs]         [-st TYPE of SCALEs]     [-i INSTANCEs]
                            [-w WARNINGs]       [-wt TYPE of WARNINGs]   [-e ENUMERATION]
                            [-c CRITICALs]      [-ct TYPE of CRITICALs]  [-des DESCRIPTIONs]
                            [-del DELIMITERs]   [-rt TYPE of REGISTER]   [-conf CONFIG]

__
}

##########################################################
# parsing options
#
GetOptions(
    'help'     => sub {exec perldoc => -F => $0 or die "Kann perldoc nicht ausfuehren: $!\n";},
    'hint'     => sub {print_help(); exit 0;},
    'h=s'      => \my $host,         # host address or FQDN
    'u=i'      => \my $unit,         # unit (slave) id
    'p=i'      => \my $_port_,       # TCP-Port for ModbusTCP
    'n=i'      => \( my $count = 1 ),     # number of values to poll
    'i=s'      => \ $name[INST],          # measurement dimensional instance
    'des=s'    => \ $name[DESC],          # desription  in front of displayed values                 
    'del=s'    => \( $name[DEL] = "\n"),  # delimiter   between displayed values
    'd=i'      => \ $addr[DATA],          # start address of data  values
    's=s'      => \ $addr[SCALE],         # start address of scale values
    'dt=s'     => \ $type[DATA],          # kind of data  register
    'st=s'     => \ $type[SCALE],         # kind of scale register
    'w=s'      => \ $addr[WARN],          # start address of lower warning  values
    'c=s'      => \ $addr[CRIT],          # start address of lower critical values
    'wt=s'     => \ $type[WARN],          # address type  of lower warning  values
    'ct=s'     => \ $type[CRIT],          # address type  of lower critical values
    'fc=s'     => \( my $fc = _MB_RHR_ ),  # address type  of lower critical values
    'conf=s'   => \my $path_conf,    # config file or path to config file
    'e=s'      => \my $enum,         # enumeration in front of the description
    'noprefix' => sub { $_PREFIX_  = _NO_;}
);


my $port       = $_port_ || _MB_DEFAULT_PORT_;

##########################################################
# checking options 
#

my $message = "";


$message .= " # ( Option -h  ): Unbekannter Host.\n"
    if ( not defined $host or $host eq "" );

$message .= " # ( Option -u  ): Unbekannte {Slave,Unit}-ID.\n"
    if ( not defined $unit or $unit eq "" );

$message .= " # ( Option -d  ): Unbekannte {Daten,Start}-Adresse.\n"
    if ( not defined $addr[DATA] or $addr[DATA] eq "" );

$message .= " # ( Option -fc ): Unbekannter Funktions-Code.\n"
    if ( not defined $fc or ( $fc ne _MB_RHR_ and $fc ne _MB_RIR_ ));

# Argumente für Description und Instance nach @DATA schreiben
foreach my $REGS ( INST, DESC, DEL )
{
    if ( defined $name[$REGS] )
    {
        # Anzahl der Elemente
        my @tmp;
        my $tmp_mod;

        if ( $name[$REGS] eq "," )
        {
            @tmp     = ("","");
            $tmp_mod = 2;
        }
        else
        {
            @tmp     = split ( /[,]/ , $name[$REGS] );
            $tmp_mod = scalar (@tmp);
        }
        $tmp_mod = 1 if ( $tmp_mod eq 0 );

        # Wenn 1 Description für alle gleich sind...
        if ($tmp_mod eq 1 and $REGS eq DESC)
        {
            # ... dann diese nur ins 1. @DATA-Element schreiben
            $DATA[$REGS][0] = $name[$REGS];
        }
        else
        {
            # Bei Instances und mehrere Descriptions...
            # werden alle auf die @DATA-Elemente verteilt
            for ( my $id = 0 ; $id < $count ; $id++ )
            {
                $tmp[$id % $tmp_mod] = "\n"
                    if ($tmp[$id % $tmp_mod] eq "\\n");
          
                $DATA[$REGS][$id] = $tmp[$id % $tmp_mod];
            }
           
        }
    }
}

# Initiierung von Enumerationen
my ($start, $step);
if ( defined $enum )
{
    ($start, $step ) = split (":",$enum);
    $message .= " # ( Option -e ): Ungueltiges Argument!\n"
        if ( $start !~ /[\d]+/ or $step !~ /^[rl]?[1-9]+/ );
}

if ( $message ne "" )
{
    print_help();
    print_error("$message");
}



##########################################################
# conffile definition
#

### Nur anlegen, wenn die Option mit Argument angegeben wurde

my $file_conf
    if ( defined $path_conf );


### /etc/icinga/eems/ + rms1-nbg3.hs.noris.de-1
### Wenn nur Verzeichnis angegeben wurde, dann Dateiname selber bauen

$file_conf = "$path_conf$host-$unit"
    if ( defined $path_conf and $path_conf =~ /\/$/ );


### /etc/icinga/eems/ + rms1-nbg3.hs.noris.de-1-1234
### Wenn wir bool-Werte haben, dann noch das Start-Register dranhängen

$file_conf .= "-$addr[DATA]"
    if ( defined $path_conf and defined $addr[DATA] and $type[DATA] eq "bool");


### /etc/icinga/eems/inividual
### Wenn es sich um einen Dateinamen handelt, dann nur den angeben.

$file_conf = $path_conf
    if ( defined $path_conf and $path_conf !~ /\/$/ );


##########################################################
# scale, data, warning and critical
#

# set dafault value if type not set
foreach my $REGS (SCALE, DATA, WARN, CRIT)
{
    # Standard der Register-Typen
    if ( not defined $type[$REGS] or $type[$REGS] eq "" )
    {
        ( $REGS eq DATA ) ?
        ( $type[$REGS] = "int" ) :  # DATA-Type = int
        ( $type[$REGS] = "FIX" ) ;  # else-Type = FIX
    }
}

# Wir beginnen mit der Datenverarbeitung
# Als aller erstes mit der Skalierung SCAL
foreach my $REGS (SCALE, DATA, WARN, CRIT)
{

    given ( $type[$REGS] )
    {
        # Keine Registerabfrage - Fixe Datenzuweisung per Argumente
        when ( "FIX" )
        {
            # Nun die Standard-Werte zuweisen für alles außer BOOL
            if ( $type[DATA] ne "bool" )
            {
                # Falls keine Werte in den Argumenten stehen: Standard = 0
                if ( not defined $addr[$REGS] or $addr[$REGS] eq "" )
                {
                    @{$DATA[$REGS]} = ( 0 ) x ( $count );
                }
                else
                {
                    if ( $REGS ne DATA )
                    {
                        ##################################
                        ### jetzt wird's a weng tricky ###
                        ##################################

                        # zu erst werden die gruppen gesplittet
                        # 1:3 5:8,-3:n3 n5,6 8 1
                        # 1:3 5:8   <-->   -3:n3 n5   <-->  6 8 1

                        my @groups  = split (/,/, $addr[$REGS]);

                        # ... und die Anzahl der Gruppen festgehalten
                        my $grp_mod = scalar @groups;

                        for (my $grp = 0 ; $grp < $count ; $grp++ )
                        {

                            # danach werden die Bereiche bearbeitet
                            # -3:n3 n5
                            # -3:n3  <-->  n5

                            my @ranges = split (/[ ]/, $groups[$grp % $grp_mod]);
                            my @values;
                            
                            for ( my $rng = 0 ; $rng < scalar @ranges ; $rng++ )
                            {
                            
                                # zum schluss werden die Werte aufgeteilt
                                # -3:n3
                                # -3 <--> n3

                                # Aber nur, wenn es MIN- und/oder MAX-Werte gibt
                                if ( $ranges[$rng] =~ /[:]/ )
                                {
                                    @values = split (/[:]/,$ranges[$rng]);
                            
                                    # falls bereiche in der schreibform ":1000" oder "12:"...
                                    foreach my $pos (MIN, MAX)
                                    {
                                        # dann vor und nach ":" mit "x" ersetzen wenn leer
                                        $values[$pos] = "x"
                                        if ( not defined $values[$pos] or
                                             $values[$pos] !~ /^[n]?[-]?\d+\.?\d*/);
                                    }
                                    if (not defined $DATA[$REGS][$grp])
                                    {
                                        $DATA[$REGS][$grp] = "$values[MIN]:$values[MAX]";
                                    }
                                    else
                                    {
                                        $DATA[$REGS][$grp] = join(' ',$DATA[$REGS][$grp],"$values[MIN]:$values[MAX]");
                                    }
                                }

                                # Andernfalls ... Moment, das muss hier noch überprüft werden!
                                else
                                {
                                    if (not defined $DATA[$REGS][$grp])
                                    {
                                        $DATA[$REGS][$grp] = $ranges[$rng];
                                    }
                                    else
                                    {
                                        $DATA[$REGS][$grp] = join(' ',$DATA[$REGS][$grp],$ranges[$rng]);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            else
            {
                if ( $REGS eq WARN or $REGS eq CRIT )
                {
                    @{$DATA[$REGS]} = ( $default_bool[$REGS] ) x ( $count )
                        if ( not defined $addr[$REGS] );
                        
                    if ( defined $addr[$REGS] and $addr[$REGS] ne "" )
                    {
                        my $num = length($addr[$REGS]);

                        print_error("Anzahl der im '$reg_type[$REGS]' angegebenen Bits stimmen nicht mit den in -n ueberein!\n")
                            if ( $num ne $count and $num ne 1);

                        # Das ist eine Sonderform und muss anders zugewiesen werden:
                        for (my $bit = 0 ; $bit < $num ; $bit++)
                        {
                            $DATA[$REGS][$bit] = substr($addr[$REGS],(-1-$bit),1);
                        }
                    }
                }
            }
        }

        # Registerabfrage - Datenzuweisung über Modbus-Register:
        when ( @valid_types )
        {

            if ( $REGS eq DATA or $REGS eq SCALE )
            {

                my $scalerepeat = 0;
                my $repeater = $count;

                    # Wenn eine Registeradresse zum Skalieren aller
                    # Datenwerte verwendet werden soll

                if ( $addr[$REGS] =~ /^[r]\d+/ and $REGS eq SCALE )
                {
                    $addr[$REGS] =~ s/r//g;
                    $count = 1;
                    $scalerepeat = 1;
                }

                # Daten abrufen
                my $connect;
                $connect->{'proto'}  = "tcp";
                $connect->{'host'}   = $host;
                $connect->{'slave'}  = $unit;
                $connect->{'port'}   = $port;

                my $access;
                $access->{'dataaddr'} = $addr[$REGS];
                $access->{'datatype'} = $type[$REGS];
                $access->{'count'}    = $count;

                @{$DATA[$REGS]} = modbus (
 
                    $connect,
                    $fc,
                    $access
 
                ) or exit _UNKN_;

                # Falls wir x Daten angefordert haben, aber y Stück zurück kommen
                print_rcv_error_and_exit("$reg_type[$REGS]", $count, scalar (@{$DATA[$REGS]}) )
                    if ( scalar @{$DATA[$REGS]} != $count );

                $count = $repeater;

                @{$DATA[$REGS]} = ( @{$DATA[$REGS]} ) x ( $repeater )
                    if ( $scalerepeat eq 1 );

            }
            else
            {

                my @ranges = split (/[ ]/, $addr[$REGS]);

                for (my $rng = 0 ; $rng < scalar @ranges ; $rng++)
                {

                    my @registers = split (/:/,$ranges[$rng]);

                    my @n;
                    my @val;

                    if ( $ranges[$rng] =~ /[:]/ )
                    {

                        foreach my $pos (MIN, MAX)
                        {
                            if ( not defined $registers[$pos] or
                                 $registers[$pos] !~ /^[n]?\d+/)
                            {
                                $n[$pos] = "";
                                @{$val[$pos]} = ( "x" ) x ($count);
                            }
                            else
                            {

                                if ( substr($registers[$pos],0,1) eq "n" )
                                {
                                    $n[$pos] = "n";
                                    $registers[$pos] =~ s/n//g;
                                }
                                else
                                {
                                    $n[$pos] = "";
                                }
                                if ( $type[$REGS] ne "FIX" )
                                {
                                    my $connect;
                                    $connect->{'proto'}  = "tcp";
                                    $connect->{'host'}   = $host;
                                    $connect->{'slave'}  = $unit;
                                    $connect->{'port'}   = $port;

                                    my $access;
                                    $access->{'dataaddr'} = $registers[$pos];
                                    $access->{'datatype'} = $type[$REGS];
                                    $access->{'count'}    = $count;

                                    @{$val[$pos]} = modbus (
                                        $connect,
                                        $fc,
                                        $access
                                    ) or exit _UNKN_;

                                }
                            }
                        }
                        for ( my $id = 0 ; $id < $count ; $id++ )
                        {
                            $DATA[$REGS][$id] = ""
                                if ( not defined $DATA[$REGS][$id] );

                            $DATA[$REGS][$id] .= $n[MIN] . $val[MIN][$id] . ":" . $n[MAX] . $val[MAX][$id];
                        }
                    }
                    for ( my $id = 0 ; $id < $count ; $id++ )
                    {
                        $DATA[$REGS][$id] .= " " if ( $rng < (scalar @ranges) - 1 );
                    }
                }
            }
        }

        # Wenn kein gültiger Datentyp angegeben wurde
        default
        {
            print_error("\tUnbekannter Datentyp: '$REGS'\n");
        }
    }
}


#######################
# parsing config file #
#######################

if ( defined $file_conf )
{

    open(IN, '<', "$file_conf") or die "Konnte '$file_conf' nicht zum Lesen öffnen.\n";


    my @name = ("") x ( $count );

    my $line_id=0;
    my $line_cnt;

    while (<IN>)
    {
        $line_cnt = $line_id + 1;

        my $line = $_;
        chomp $line;

        $line =~ s/^#[ ]*//g if ( $line =~ /^#/ );

        my ( @optargs ) = split (/ +/ , $line);

        foreach my $optarg (@optargs)
        {
            my ( $opt, $arg ) = split (/=/ , $optarg);

            if (defined $opt and defined $arg)
            {
                $arg =~ s/_/ /g;

                $DATA[SCALE][$line_id] = $arg if ($opt eq "s");
                $DATA[WARN][$line_id]  = $arg if ($opt eq "w");
                $DATA[CRIT][$line_id]  = $arg if ($opt eq "c");
                $DATA[DESC][$line_id]  = $arg if ($opt eq "des");
                $DATA[INST][$line_id]  = $arg if ($opt eq "i");
            }
        }
        $line_id++;
    }

    if ( $line_cnt ne $count )
    {
        print STDERR "Anzahl der Zeilen in Datei '$file_conf' ($line_cnt) stimmt nicht mir der Anzahl der geforderten Wert (-n $count) überein.\n";
        exit _UNKN_;
    }
}

for ( my $id = 0 ; $id < $count ; $id++ )
{
    $DATA[SCALE][$id] = 0
        if (not defined $DATA[SCALE][$id]);

    $DATA[DATA][$id] *= 10 ** $DATA[SCALE][$id];
}


my @HIGHLIGHT;
$HIGHLIGHT[WARN] = "*";
$HIGHLIGHT[CRIT] = "**";

if ($type[DATA] ne "bool")
{

    for (my $id = 0 ; $id < $count ; $id++)
    {
        $DATA[STAT][$id] = "";

        foreach my $REGS (WARN, CRIT)
        {

            my @ranges = split (/[ ]/, $DATA[$REGS][$id]);
            my $rng_count = scalar (@ranges);
            my $and_stat = 0;
            
            for (my $rng = 0 ; $rng < $rng_count ; $rng++)
            {
            
                #################
                #  "MIN : MAX"  #
                #################
                if ( $ranges[$rng] =~ /:/ )
                {
                    my (@val) = split (/:/,$ranges[$rng]);
           
 
                    ###########
                    #  "x:x"  #
                    ###########
                    if ( $val[MIN] eq "x" and $val[MAX] eq "x" )
                    {
                        $DATA[STAT][$id] .= "x"
                            if ($DATA[STAT][$id] ne "*");
                    }


                    ###############
                    #  "x : MAX"  #
                    ###############
                    elsif ( $val[MIN] eq "x" )
                    {
                        if ($val[MAX] =~ /^[n]/)
                        {
                            $val[MAX] =~ s/n//g;
            
                            $DATA[STAT][$id] = $HIGHLIGHT[$REGS]
                                if ($DATA[DATA][$id] < $val[MAX]);
                        }
                        else
                        {
                            $DATA[STAT][$id] = $HIGHLIGHT[$REGS]
                                if ($DATA[DATA][$id] <= $val[MAX]);
                        }
                    }


                    ###############
                    #  "MIN : x"  #
                    ###############
                    elsif ( $val[MAX] eq "x" )
                    {
            
                        if ($val[MIN] =~ /^[n]/)
                        {
                            $val[MIN] =~ s/n//g;
            
                            $DATA[STAT][$id] = $HIGHLIGHT[$REGS]
                                if ($DATA[DATA][$id] > $val[MIN]);
                        }
                        else
                        {
                            $DATA[STAT][$id] = $HIGHLIGHT[$REGS]
                                if ($DATA[DATA][$id] >= $val[MIN]);
                        }
                    }


                    #################
                    #  "MIN : MAX"  #
                    #################
                    else
                    {
                        if ($val[MIN] =~ /^[n]/ and $val[MAX] =~ /^[n]/ )
                        {
                            $val[MIN] =~ s/n//g;
                            $val[MAX] =~ s/n//g;

                            if ( $val[MIN] < $DATA[DATA][$id] and $val[MAX] > $DATA[DATA][$id] )
                            {
                                $DATA[STAT][$id] = $HIGHLIGHT[$REGS];
                            }
                        }
                        elsif ($val[MIN] !~ /^[n]/ and $val[MAX] !~ /^[n]/ )
                        {
                            if ( $val[MIN] <= $DATA[DATA][$id] and $val[MAX] >= $DATA[DATA][$id] )
                            {
                                $DATA[STAT][$id] = $HIGHLIGHT[$REGS];
                            }
                        }
                        elsif ($val[MIN] =~ /^[n]/ and $val[MAX] !~ /^[n]/ )
                        {
                            $val[MIN] =~ s/n//g;

                            if ( $val[MIN] <= $DATA[DATA][$id] and $val[MAX] > $DATA[DATA][$id] )
                            {
                                $DATA[STAT][$id] = $HIGHLIGHT[$REGS];
                            }
                        }
                        elsif ($val[MIN] !~ /^[n]/ and $val[MAX] =~ /^[n]/ )
                        {
                            $val[MAX] =~ s/n//g;

                            if ( $val[MIN] < $DATA[DATA][$id] and $val[MAX] >= $DATA[DATA][$id] )
                            {
                                $DATA[STAT][$id] = $HIGHLIGHT[$REGS];
                            }
                        }
                    }
                }

                ###########################
                #  "VALUE VALUE ..."  #
                ###########################
                else
                {
                    if ( $ranges[$rng] =~ /^n/ )
                    {
                        $ranges[$rng] =~ s/n//;
            
                        $and_stat++
                            if ($DATA[DATA][$id] != $ranges[$rng]);
            
                        $DATA[STAT][$id] = $HIGHLIGHT[$REGS]
                            if ( ( $rng + 1 ) eq $rng_count and ( $and_stat eq $rng_count ) );
                    }
                    else
                    {
                        $DATA[STAT][$id] = $HIGHLIGHT[$REGS]
                            if ($DATA[DATA][$id] == $ranges[$rng]);
                    }
                }
            }
        }
    }
}

my $STATUS = _OKAY_;
my $OUTPUT = "";

my @mod;

foreach my $REGS (WARN, CRIT, DESC, INST)
{
    $mod[$REGS] = 1;

    $mod[$REGS] = scalar (@{$DATA[$REGS]})
        if (defined @{$DATA[$REGS]});
}

my @tmp_array = ( "" );

@tmp_array = @{$DATA[DESC]}
    if (defined @{$DATA[DESC]});

my $offset = 0;

for ( my $id = 0 ; $id < $count ; $id++ )
{
    ############
    # not BOOL #
    ############
    if ($type[DATA] ne "bool")
    {

        ############
        # WARNINGS #
        ############
        if ( $DATA[STAT][$id] eq "*" )
        {
            $DATA[DATA][$id] = "* $DATA[DATA][$id] *";
            $STATUS = _WARN_ if ( $STATUS ne _CRIT_ );
        }
        
        ############
        # CRITICAL #
        ############
        if ( $DATA[STAT][$id] eq "**" )
        {
            $DATA[DATA][$id] = "** $DATA[DATA][$id] **";
            $STATUS = _CRIT_;
        }
    }

    ############
    # BOOL     #
    ############
    else
    {


        ############
        # WARNINGS #
        ############
        if ( $DATA[DATA][$id] eq $DATA[WARN][$id] )
        {
            $DATA[DATA][$id] = "* $DATA[DATA][$id] *";

            $STATUS = _WARN_
                if ( $STATUS ne _CRIT_ );
        }

        ############
        # CRITICAL #
        ############
        if ( $DATA[DATA][$id] eq $DATA[CRIT][$id] )
        {
            $DATA[DATA][$id] = "** $DATA[DATA][$id] **";
            $STATUS = _CRIT_;
        }
    }

    if (not ($DATA[WARN][$id] eq "x" and $DATA[CRIT][$id] eq "x"))
    {

        ###############
        # first Value #
        ###############
        if ( $id eq $offset )
        {
            if ( defined @{$DATA[DESC]} and $mod[DESC] gt 0 )
            {
                if ( defined $start )
                {
                    if ( $step !~ /[rl]/ )
                    {
                        $OUTPUT = "$DATA[DESC][$id]" . ( $start + ($id * $step) ) . ": ";
                    }
                    elsif ($step =~ /[r]/ )
                    {
                        my $tmp = $step;
                        $tmp =~ s/[r]//;
                        $OUTPUT = "$DATA[DESC][$id]" . ( $start + ($id / $tmp) ) . ": "
                            if ( $tmp ne 0 );
                    }
                    elsif ($step =~ /[l]/ )
                    {
                        my $tmp = $step;
                        $tmp =~ s/[l]//;
                        $OUTPUT = "$DATA[DESC][$id]" . ( $start + ($id % $tmp) ) . ": "
                            if ( $tmp ne 0 );
                    }

                }
                else
                {
                    (defined $DATA[DESC][$id] and $DATA[DESC][$id] ne "") ?
                    ( $OUTPUT = "$DATA[DESC][$id]: " ) :
                    ( $OUTPUT = "" );
                }
            }
            else
            {
                ( defined $start ) ?
                ( $OUTPUT = $start + ($step*$id) . ": " ) :
                ( $OUTPUT = "" ) ;
 
            }
            $OUTPUT .= $DATA[DATA][$id];
            $OUTPUT .= $DATA[INST][$id]
                if (defined $DATA[INST][$id]);
        }

        ###############
        # other Value #
        ###############
        else
        {
            if ( defined $start )
            {
                if ( $step !~ /[rl]/ )
                {
                    $DATA[DESC][$id]  = "$tmp_array[$id % $mod[DESC]]" . ( $start + ($step * $id)) . ": ";
                }
                elsif ($step =~ /[r]/ )
                {
                    my $tmp = $step;
                    $tmp =~ s/[r]//;
                    $DATA[DESC][$id]  = "$tmp_array[$id % $mod[DESC]]" . ( $start + floor($id / $tmp)) . ": "
                        if ( $tmp ne 0 );
                }
                elsif ($step =~ /[l]/ )
                {
                    my $tmp = $step;
                    $tmp =~ s/[l]//;
                    $DATA[DESC][$id]  = "$tmp_array[$id % $mod[DESC]]" . ( $start + ($id % $tmp)) . ": "
                        if ( $tmp ne 0 );
                }
            }
            else
            {
                $DATA[DESC][$id] = "$tmp_array[$id % $mod[DESC]]" . ": ";
            }
 
            if ( $mod[DESC] eq 1 )
            {
                ( defined $DATA[INST][$id] ) ?
                ( $OUTPUT = join ($DATA[DEL][$id-1], $OUTPUT, "$DATA[DATA][$id]$DATA[INST][$id]") ) :
                ( $OUTPUT = join ($DATA[DEL][$id-1], $OUTPUT, "$DATA[DATA][$id]") ) ;
            }
            else
            {
                ( defined $DATA[INST][$id] ) ?
                ( $OUTPUT = join ($DATA[DEL][$id-1], $OUTPUT, "$DATA[DESC][$id]$DATA[DATA][$id]$DATA[INST][$id]") ) :
                ( $OUTPUT = join ($DATA[DEL][$id-1], $OUTPUT, "$DATA[DESC][$id]$DATA[DATA][$id]") ) ;
            }
        }
    }
    else
    {
        $offset++;
    }

}


if ($_PREFIX_)
{
    $OUTPUT = $PREFIX_MESSAGE[$STATUS] . $OUTPUT;
}

print $OUTPUT . "\n";

exit $STATUS;


__END__

=encoding utf8

=head1 NAME

    check_modbus - Datenabfrage via ModbusTCP

=head1 DESCRIPTION

    check_modbus ermittelt Daten via ModbusTCP aus Geräten und überprüft sie auf unterschiedliche Weise auf ihre Werte.

=head1 SYNOPSIS

    check_modbus  -h HOST  -u UNIT  -d DATA
                            [-c COUNT]          [-dt TYPE of DATA]       [-p PORT]
                            [-s SCALEs]         [-st TYPE of SCALEs]     [-i INSTANCEs]
                            [-w WARNINGs]       [-wt TYPE of WARNINGs]   [-e ENUMERATION]
                            [-c CRITICALs]      [-ct TYPE of CRITICALs]  [-des DESCRIPTIONs]
                            [-del DELIMITERs]   [-rt TYPE of REGISTER]   [-conf CONFIG]

=head1 OPTIONS

=head2 Required Options

=over 1

=item B<-h HOST>

Hier wird die FQDN des Hosts angegeben.

=item B<-u UNIT>

Auch bekannt unter dem Namen Slave-ID.
Das ist jenes Gerät, welches im BUS-System mit einer eindeutigen Nummer identifiziert wird.

 => Wertebereich: 1 Byte (2^8 => 0-255)

=item B<-d DATA>

B<DATA> gibt die I<normale> Datenadresse eines Modbus-Gerätes im anzusprechenden Modbus-System an.
In der Regel sind das die Adressen, welche man in den Bedienungsanleitungen der Hersteller findet.
Stehen in den Anleitungen Hexadezimal-Werte, so muss hier ggf. eine um eins höhere Zahl angegeben werden.
Normale (nicht PDU) Datenadresse eines Modbus-Gerätes im anzusprechenden Modbus-System.
In der Regel sind das die Adressen, welche man in den Bedienungsanleitungen der Hersteller findet.

 => Wertebereich: 2 Byte (2^16 => 0-65535 [PDU-Adressen], 1-65536 [I<normale> Adressen])

=back

Beispiel:

    $ > check_modbus -h modbus-gateway.domain.com -u 3 -d 1234


=head2 Additional Options (with default sets)

=over 1

=item B<-n COUNT>

Mit B<COUNT> wird die Anzahl der abzufragenden Daten angegeben.
Betonung liegt auf Anzahl der Daten/Werte - I<nicht> die abzufragenden Register.
Hierbei muss der Anwender bei unterschiedlichen Datentypen sich keine Gedanken über die Anzahl der abzufragenden Register machen.

 => Default: 1


=item B<-p PORT>

Falls erforderlich, kann hier mit B<PORT> ein vom Standard abweichender TCP-Port angegeben werden.

 => Default: 502


=item B<-fc FUNCTION_CODE>

Mit B<-fc> kann man den Funktions-Code (Function Code) des abzufragenden Modbus-Registers angeben. Mögliche Werte für B<FUNCTION_CODE> sind:

  * 3 (=> Read Holding Registers [_MB_RHR_])
  * 4 (=> Read Input   Registers [_MB_RIR_])

 => Default: 3


=item B<-del DELIMITER>s

Relevant bei mehr als 1 abzufragenden Datenwert (B<COUNT> > 1).
B<DELIMITER> gibt an, wie die Daten bei der Ausgabe dargestellt werden sollen, d. h. welches Trennzeichen zwichen den Daten erscheinen soll.

Es ist möglich mehrere durch Kommas getrennte Trennzeichen anzugeben. Bei n Trennzeichen werden die Trennzeichen im Modulo-n-Verfahren zwischen den Messwerten stehen.

Beispiel:

    $ > check_modbus ... -del " / ,\n"

Hier wird:
 *  zwischen dem 1. und dem 2. Datenwert ein " / " stehen
 *  zwischen dem 2. und dem 3. Datenwert eine neue Zeile '\n' kommen
 *  zwischen dem 3. und dem 4. Datenwert ein " / " stehen
 *  ... 

 => Default: '\n' 

=item B<-des DESCRIPTION>s

Mit der Angabe I<einer> B<DESCRIPTION> wird vor dem I<ersten> Datenwert einmalig die angegebene Zeichenkette vorangestellt.

Beispiel:

    $ > check_modbus ... -des "Wert: "

Mit der Angabe mehrerer durch Kommas getrennte DESCRIPTIONs werden diese bei n Beschreibungen im Modulo-n-Verfahren vor jedem Datenwert vorangestellt.

Beispiel:

    $ > check_modbus ... -des "Spannung: ,Strom: "

  => Präfix des 1. Datnenwerts: "Spannung: "
  => Präfix des 2. Datnenwerts: "Strom: "
  => Präfix des 3. Datnenwerts: "Spannung: "
  => Präfix des 4. Datnenwerts: "Strom: "
  => ... 

=item B<-i INSTANCE>s

Im Prinzip wird mit B<INSTANCE> genau so verfahren wie mit B<DESCRIPTION>, jedoch findet sich die Zeichenkette als Suffix hinter I<jedem> Messwert.

Beispiel:

    $ > check_modbus ... -des "Spannung: ,Strom: " -i " V, A"

=item B<-e ENUMERATION>

Die Enumeration wird mit -e eingeleitet und bietet eine Nummerierung der Datenwerte. Dabei sind 3 Variationen mit folgendem Syntax möglich: 

I<Simple Numeration>:  Bietet einen Startwert und eine Schrittfolge. 


Beispiel 1:

    $ > check_modbus ... -e 1:1

Nummeriert die Datenwerte in der Folge: 1-2-3-4-5-6-...


Beispiel 2:

    $ > check_modbus ... -e 0:2

Nummeriert die Datenwerte in der Folge: 0-2-4-6-... 


I<Repeat Numeration>:  Bietet einen Startwert und eine Wiederholungsanzahl, bevor um eins inkrementiert wird. 

Beispiel:

    $ > check_modbus ... -e 1:r3

Nummeriert die Datenwerte in der Folge: 1-1-1-2-2-2-3-3-... 


I<Loop Numeration>:  Bietet einen Startwert und das Maximum, bevor die Numeration wiederholt wird. 

Beispiel:

    $ > check_modbus ... -e 1:l4

Nummeriert die Datenwerte in der Folge: 1-2-3-1-2-3-1-2-... 



=item B<->[B<d>,B<s>,B<w>,B<c>]B<t>: B<TYPE:> ...

Gibt den Datentyp an, wie das jeweilige Argument für B<-d>, B<-s>, B<-w> und B<-c> interpretiert werden soll:

=over 5

B<-dt>: Datentyp für die Option B<-d>

B<-st>: Datentyp für die Option B<-s>

B<-wt>: Datentyp für die Option B<-w>

B<-ct>: Datentyp für die Option B<-c>

=back


Mögliche Datentypen:
  * int
  * uint
  * int32
  * uint32
  * float
  * quad
  * bool (nur für B<-dt>)
  * FIX


=over 3

=item ... B<-dt TYPE:DATA>

Mit dieser Option wird der Datentyp des Registers angegeben, aus dem die Messwerte (Nutzdaten) abgefragt werden.
Beim Datentyp FIX allerdings werden keine Register abgefragt.
In diesem Fall wird das Argument unter -d (DATA) als konstanter Messwert angenommen (u. a. für Testzwecke relevant).

  => Default: int


=item ... B<-st TYPE:SCALE>

Mit dieser Option wird der Datentyp des Registers angegeben, aus dem die Skalierung der Nutzdaten abgefragt werden.
Beim Datentyp FIX allerdings werden keine Register abgefragt.
In diesem Fall wird das Argument unter -s als konstante Skalierung angenommen.

  => Default: FIX


=item ... B<-wt TYPE:WARNING>, B<-ct TYPE:CRITICAL>

Mit dieser Option wird der Datentyp des Registers angegeben, aus dem die WARNING/CRITICAL-Bereiche abgefragt werden.
Beim Datentyp FIX allerdings werden keine Register abgefragt.
In diesem Fall wird das Argument unter B<-w>/B<-c> als konstanter WARNING/CRITICAL-Wert angenommen.

  => Default: FIX

=back

=back

=over 1

=item B<-s SCALE>s

Abhängig vom Datentyp (B<-st>) wird B<SCALE> unterschiedlich behandelt. Außerdem wird B<SCALE> bei B<TYPE:DATA> = bool nicht ausgewertet:

=over 3

=item für B<TYPE:SCALE>=FIX

Bei B<TYPE:SCALE> = FIX, wird B<SCALE> nicht aus den Registern ermittelt.
Hierbei wird B<SCALE> als konstanter Wert angenommen.
B<SCALE> kann folgender Maßen übergeben werden: 

 1) mit einem Argument (dann wird sie auf alle Daten angewendet)
 2) mit mehreren Argumenten in Anführungszeichen durch Komma getrennt

Beispiel 1 - Einfache Skalierung aller Werte:

    $ > check_modbus -h my.domain.tld -d 1214 -n 10 -s -2 -st FIX

Die Nachkommastelle aller B<COUNT>=10 erhobenen Messwerte werden ab Register B<DATA>=1214 FIX um B<SCALE>=-2 nach links verschoben (wäre B<SCALE> positiv, würde das Komme in die andere Richtung nach rechts rutschen):

    Messwert = Registerwert * Skalierung
    Messwert[i] = Registerwert(DATA[i]) * 10 ^ SCALE
    Messwert[0] = Registerwert(1214) * 10 ^ − 2
    Messwert[1] = Registerwert(1215) * 10 ^ − 2
    Messwert[2] = Registerwert(1216) * 10 ^ − 2
    Messwert[3] = Registerwert(1217) * 10 ^ − 2
    [...]

Beispiel 2 - Individuelle Skalierung einzelner Werte:

    $ > check_modbus -h my.domain.tld -d 1214 -dt int32 -n 10 -s "-2,4,0" -st FIX

Bei n Skalierwerte werden diese nacheinander im Modulo-n-Verfahren für die Werte aus B<DATA> angewendet. In diesem Beispiel: 3 Werte -> Modulo 3 (%3 -> 0,1,2 - 0,1,2 - 0,1,2 - ...).

    Messwert = Registerwert * Skalierung
    Messwert[i] = Registerwert(DATA[i]) * 10 ^ SCALE[i%3]
    Messwert[0] = Registerwert(1214) * 10 ^ − 2
    Messwert[1] = Registerwert(1216) * 10 ^ 4
    Messwert[2] = Registerwert(1218) * 10 ^ 0
    Messwert[3] = Registerwert(1220) * 10 ^ − 2
    [...]


=item für B<TYPE:SCALE>=int|uint|int32|uint32

Für B<TYPE:SCALE> = *int*, wird B<SCALE> als Datenadresse behandelt. Die Skalierung wird aus den Registern ermittelt und als Exponent mit der Basis 10 mit dem Wert aus B<DATA> multipliziert.

Beispiel:


    $ > check_modbus ... -d 1214 -s 1014 -st int -n 10

Die Nachkommastelle aller B<COUNT>=10 erhobenen Messwerte ab Register B<DATA>=1214 werden um die Stellen nach links oder rechts verschoben, die aus dem Register B<SCALE>=1014 abgefragt wurden:
     Messwert = Registerwert * Skalierung
     Messwert[i] = Registerwert(DATA[i]) * 10 ^ Registerwert(SCALE[i])
     Messwert[0] = Registerwert(1214) * 10 ^ Registerwert(1014)
     Messwert[1] = Registerwert(1215) * 10 ^ Registerwert(1015)
     Messwert[2] = Registerwert(1216) * 10 ^ Registerwert(1016)
     [...]


=item für B<TYPE:SCALE>=float|quad

    ToDo (noch nicht aktiv) 
    Für B<TYPE:SCALE> = [float | quad] wird B<SCALE> als Registeradresse behandelt. Die Skalierung wird aus den Registern ermittelt und mit dem Wert aus B<DATA> multipliziert.

Beispiel:

    $ > check_modbus ... -d 1214 -s 1014 -st float -n 10

Die Werte aller B<COUNT>=10 erhobenen Messwerte ab Register B<DATA>=1214 werden mit dem Werten ab Register B<SCALE>=1014 multipliziert:

     Messwert = Registerwert * Skalierung
     Messwert[i] = Registerwert(DATA[i]) * Registerwert(SCALE[i])
     Messwert[0] = Registerwert(1214) * Registerwert(1014)
     Messwert[1] = Registerwert(1215) * Registerwert(1016)
     Messwert[2] = Registerwert(1216) * Registerwert(1018)
     [...]

=item Repeat-Funktion für B<SCALE>

Wird der Skalieradresse ein "B<r>" vorangestellt, so wird nur die angegebene Skalieradresse abgerufen und auf alle Datenwerte abhängig vom Skalier-Typ angewendet.

Beispiel:

    $ > check_modbus ... -d 1214 -s r1014 -st int -n 10

Die Werte aller B<COUNT>=10 erhobenen Messwerte ab Register B<DATA>=1214 werden ALLE mit dem Wert aus Register B<SCALE>=1014 multipliziert:

     Messwert = Registerwert * Skalierung
     Messwert[i] = Registerwert(DATA[i]) * 10 ^ Registerwert(SCALE[1014])
     Messwert[0] = Registerwert(1214) * 10 ^ Registerwert(1014)
     Messwert[1] = Registerwert(1215) * 10 ^ Registerwert(1014)
     Messwert[2] = Registerwert(1216) * 10 ^ Registerwert(1014)
     [...]

=back

=item B<-w WARNING>s, B<-c CRITICAL>s

Abhängig von B<TYPE:WARNING> und -dt B<TYPE:DATA> wird WARNING unterschiedlich behandelt:

=over 3

=item B<TYPE:DATA>=bool

Wenn TYPE:DATA den Wert bool hat, werden die Argumente für B<WARNING> B<CRITICAL> Bitweise verarbeitet.
Nähere Infos über den Syntax für bool-Werte siehe unter SYNTAX/bool.

    => Default WARNING : x
    => Default CRITICAL: 1

=item B<TYPE:WARNING>=FIX, B<TYPE:CRITICAL>=FIX

Wenn B<TYPE:WARNING> den Wert FIX hat, werden die Argumente für B<WARNING> nicht aus den Register abgelesen, sondern als konstannte WARNING-Werte angenommen.

Wenn B<TYPE:CRITICAL> den Wert FIX hat, werden die Argumente für B<CRITICAL> nicht aus den Register abgelesen, sondern als konstannte CRITICAL-Werte angenommen.

Überschneiden sich WARNING- und CRITICAL-Werte, so gewinnt immer CRITICAL. Nähere Infos über den Syntax für FIX-Werte, siehe SYNTAX/FIX.

    => Default WARNING : 0
    => Default CRITICAL: 0

=back

=item Modulo-n

Das Modulo-n-Verfahren wird nur dann eingesetzt, wenn mehr Daten abgefragt werden (B<COUNT> > 1), als in den Argumenten vorhanden sind. Dies findet bei folgenden Optionen statt:

    -s B<SCALE>s
    -i B<INSTANCES>s
    -w B<WARNING>s
    -c B<CRITICAL>s
    -del B<DELIMITER>s
    -des B<DESCRIPTION>s 

Prinzipiell erfolgt die Trennung der Argumente, um sie den einzelnen Datenwerte zuzuordnen, immer durch ein einfaches Komma ( , ).
Existieren mehrere Datenwerte, aber weniger Argumente, so werden die Argumente erneut auf die nächsten Datenwerte angewendet. 

Beispiel:

    $ > check_modbus ... -n 9 ... -s "-2,-1,-3" -des "T(innen),U(innen),O2(innen),T(außen),U(außen),O2(außen)," -i " °C, V, %" \
                                 -c ":15 25: ,:210 240:,18:" \
                                 -w "n15:18 23:n25, n210:216 238:n240,16: -del " / , / ,\n" -e 1:r6

Angenommene Datenwertrückgabe:

 2030
 2319
 16132
 1700
 2100
 21110
 1990
 2298
 15861

Skalierung:

 2030  * 10 ^ - 2
 2319  * 10 ^ - 1
 16132 * 10 ^ - 3
 1700  * 10 ^ - 2
 2100  * 10 ^ - 1
 21110 * 10 ^ - 3
 1990  * 10 ^ - 2
 2298  * 10 ^ - 1
 15861 * 10 ^ - 3

Ausgabe:

 T(innen)1: 20.3 °C / U(innen)1: 231.9 V / O2(innen)1: *16.132* %
 T(außen)1: *17* °C / U(außen)1: **210** V / O2(außen)1: **21.11** %
 T(innen)2: 19.9 °C / U(innen)2: 229.8 V / O2(innen)2: 15.861 %

B<Ausnahme>:
    Modulo-n wird bei -des B<DESCRIPTION> nur dann eingesetzt, wenn mehr als 1 Argument durch Kommas getrennt angegeben werden.

=item B<SYNTAX>

=over 3

=item B<TYPE:>=bool

Bei boolschen Datenwerte (B<TYPE:DATA> = bool), werden die WARNING- und CRITICAL-Werte als boolsche und fixe Bedingungen behandelt (sofern mit -wt, bzw. -ct der Default-Wert FIX nicht verändert wurde):

 * 0 : Meldet WARNING, bzw. CRITICAL, wenn DATA = false (0)
 * 1 : Meldet WARNING, bzw. CRITICAL, wenn DATA = true  (1) [default für CRITICAL]
 * x : WARNING, bzw. CRITICAL ist für DATA deaktiviert  (*) [default für WARNING]

Hinweise:
 Ist an einer Bit-Position WARNING und CRITICAL mit "x" dektiviert, so taucht das Resultat auch nicht bei der Ausgabe mit auf.
 Ist an einer Bit-Position WARNING und CRITICAL mit dem gleichen Wert (0 oder 1) aktiviert, so ist das Resultat bei erfüllter Bedingung immer CRITICAL.
 Ist an einer Bit-Position WARNING und CRITICAL mit unterschiedlichen Werten (0 oder 1) aktiviert, so ist das Resultat immer entweder WARNING oder CRITICAL.

Beispiel:
 check_modbus ... -dt bool -w 0x1x0x -c 1xx0x1
                              ^^^^^^    ^^^^^^
                              543210    543210  < Bit-Position
 
                        / CRITICAL \  |  / WARNING \
                 Bit 0:      1        |      aus
                 Bit 1:     aus       |       0
                 Bit 2:      0        |      aus
                 Bit 3:     aus       |       1
                 Bit 4:     aus       |      aus
                 Bit 5:      1        |       0

=item B<TYPE:>=FIX

Bei nicht boolschen Datenwerte (B<TYPE:DATA> != bool), werden die WARNING- und CRITICAL-Werte entsprechend der B<TYPE:WARNING>, bzw. B<TYPE:CRITICAL> behandelt. Sind B<TYPE:WARNING>, bzw. B<TYPE:CRITICAL> mit dem Wert "FIX" angegeben, werden sie folgender Maßen behandelt:

=over 4

=item Bedingungen im Überblick:

Es gibt mehrere Möglichkeiten, CRITICAL- und WARNING-Bedingungen zu übergeben:


                                         getrenntmultiple Bedingung
               /-----------------------------------------------------------------------------\
              |                                                                               |       
               multiple Bedingung            multiple Bedingung             multiple Bedingung
    /-------------------------------------\ /------------------\ /-------------------------------------\
   |                                       |                    |                                       |
   "Bedingung Bedingung Bedingung Bedingung,Bedingung  Bedingung,Bedingung Bedingung Bedingung Bedingung"
   
   "  [...]     [...]     [...]     [...]  ,  [...]      [...]  ,  [...]     [...]     [...]     [...]  "


=over 6

=item Einfache Bedingungen

Bei einer einfachen Bedingung sind folgende Argumente möglich: 

 X      WARNING, bzw CRITICAL allein fuer den Wert X.
 nX     WARNING, bzw CRITICAL fuer alle Werte ausser X.
 X:     WARNING, bzw CRITICAL fuer alle Werte ab einschliesslich X.
 :X     WARNING, bzw CRITICAL fuer alle Werte bis einschliesslich X.
 X:Y    WARNING, bzw CRITICAL fuer alle Werte ab X bis Y je einschliesslich.
 nX:    WARNING, bzw CRITICAL fuer alle Werte groesser X.
 :nX    WARNING, bzw CRITICAL fuer alle Werte kleiner X.
 nX:nY  WARNING, bzw CRITICAL fuer alle Werte zwischen X und Y.

Beispiel:

 check_modbus ... -w "10:n30" -c "30:" -wt FIX -ct FIX

Hinweis:

 Die Optionen -wt und -ct können inkl. Argument auch weggelassen werden, da sie ohne Angaben standardmäßig FIX sind.

=item Multiple Bedingungen

Es ist auch möglich, mehrere Werte und Bereiche für ein und den selben Datensatz zu übergeben. Dabei werden alle einfachen Bedingungen in Doppelhochkommas eingeschlossen und intern mit Leerzeichen ( ) getrennt übergeben.

Beispiel:

 check_modbus ... -w "10:n30 n-30:-10 200" -c "30: :-30"

Ausgabe:
     WARNING für alle Werte
        im Bereich 10 bis < 30
        im Bereich -10 bis > -30
        mit dem Wert 200 

    CRITICAL für alle Werte
        im Bereich ab 30 aufwärts
        im Bereich ab -30 abwärts 


=item Getrennt Multiple Bedingungen

CRITICAL und WARNING lassen sich auch auf individuelle Messwerte anwenden. Dabei werden die multiplen Bedingungen mit Kommas (,) getrennt.

Beispiel:

 check_modbus ... -c "X:nY nA:B,nM nN,I J"

Darstellung:

                     /-------\ /---\ /-\
                    |    1    |  2  | 3 |
                     X:nY nA:B,nM nN,I J
                     \-------/ \---/ \-/
                             \     \   \_ 3. Messwert: WARNING/CRITICAL fuer den Wert I und J
                              \     \____ 2. Messwert: WARNING/CRITICAL fuer alles ausser M und N
                               \_________ 1. Messwert: WARNING/CRITICAL ab X bis kleiner Y und groesser A bis B

Beschreibung:

 Wenn mehr als 3 Messwerte abgefragt werden ( COUNT > 3 ), so werden die Bedingungen 1 bis 3 auf die darauffolgenden Messwerte wiederholt.

 ...
 4. Messwert wird nach den Bedingungen aus 1 ausgewertet.
 5. Messwert wird nach den Bedingungen aus 2 ausgewertet.
 6. Messwert wird nach den Bedingungen aus 3 ausgewertet.
 7. Messwert wird nach den Bedingungen aus 1 ausgewertet.
 8. Messwert wird nach den Bedingungen aus 2 ausgewertet.
 ...

=over

=over

=item B<-help|?>

    um (nur) diese Dokumentation anzeigen zu lassen.

=back

Stefan Steiner (Stoni)
für die noris network AG
