Subversion Repositories Projects

Rev

Blame | Last modification | View Log | RSS feed

#!/usr/bin/perl
#!/usr/bin/perl -d:ptkdb

###############################################################################
#
# libmktime.pl -  MK Mission Cockpit - Timer for GUI Frontend
#
# Copyright (C) 2009  Rainer Walther  (rainerwalther-mail@web.de)
#
# Creative Commons Lizenz mit den Zusaetzen (by, nc, sa)
#
# Es ist Ihnen gestattet:
#     * das Werk vervielfältigen, verbreiten und öffentlich zugänglich machen
#     * Abwandlungen bzw. Bearbeitungen des Inhaltes anfertigen
#
# Zu den folgenden Bedingungen:
#     * Namensnennung.
#       Sie müssen den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen.
#     * Keine kommerzielle Nutzung.
#       Dieses Werk darf nicht für kommerzielle Zwecke verwendet werden.
#     * Weitergabe unter gleichen Bedingungen.
#       Wenn Sie den lizenzierten Inhalt bearbeiten oder in anderer Weise umgestalten,
#       verändern oder als Grundlage für einen anderen Inhalt verwenden,
#       dürfen Sie den neu entstandenen Inhalt nur unter Verwendung von Lizenzbedingungen
#       weitergeben, die mit denen dieses Lizenzvertrages identisch oder vergleichbar sind.
#
# Im Falle einer Verbreitung müssen Sie anderen die Lizenzbedingungen, unter welche dieses
# Werk fällt, mitteilen. Am Einfachsten ist es, einen Link auf diese Seite einzubinden.
#
# Jede der vorgenannten Bedingungen kann aufgehoben werden, sofern Sie die Einwilligung
# des Rechteinhabers dazu erhalten.
#
# Diese Lizenz lässt die Urheberpersönlichkeitsrechte unberührt.
#
# Weitere Details zur Lizenzbestimmung gibt es hier:
#   Kurzform: http://creativecommons.org/licenses/by-nc-sa/3.0/de/
#   Komplett: http://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode
#
###############################################################################
# 2009-08-09 0.2.5 rw Timer moved from mkcockpit.pl
#                     Optional Player home-pos in map configuration
# 2009-08-23 0.2.6 rw Tracking-Antenna Icon
#                     POI heading control
# 2009-10-11 0.2.7 rw Tracker control changed
# 2010-02-10 0.4.0 rw Altitude average changed
#                     Event Engine timer
#                     Serial channel timer
#                     External Control rimer
#                     Crosshair Timer
#                     5s timer changed to 3s
#                     Current, UsedCapacity, Power added
# 2010-02-13 0.4.1 rw bugfix JoystickButton min-value
# 2010-02-14 0.4.2 rw Input chontrol parser added
#                     Request LCD Stick/Poti
#
###############################################################################

$Version{'libmktimer.pl'}  = "0.4.1 - 2010-02-13";

use Math::Trig;
use Time::HiRes qw(gettimeofday);   # http://search.cpan.org/~jhi/Time-HiRes-1.9719/HiRes.pm

#
# Timer: 3s
#
$main->repeat (3000, sub
    {
    if ( ! $MkSendWp )
        {
        # Abfragefrequenz OSD und Debug regelmäßig neu einstellen, falls Übertragungsfehler
        $MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 10) );          # Frequenz OSD Datensatz, * 10ms
        $MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 10) );          # Frequenz MK Debug Datensatz, * 10ms
        $MkSendQueue->enqueue( "v", "$AddrNC", "");   # Version
        $MkSendQueue->enqueue( "e", "$AddrNC", "");   # Error Text Request
        }

    lock (%MkOsd);              # until end of block

    # Draw Operation Radius Border
    $map_canvas->delete('Map-Border-OperatingRadius');
    if ( &HomePosIsValid() )
        {
        my $Radius = $MkOsd{'OperatingRadius'};
        my $H_Lat = $MkOsd{'HomePos_Lat'};
        my $H_Lon = $MkOsd{'HomePos_Lon'};
        my $Angel = &MapAngel();

        my ($T_Lat, $T_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel -90);
        my ($R_Lat, $R_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel);
        my ($B_Lat, $B_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel + 90);
        my ($L_Lat, $L_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel + 180);

        my ($T_x, $T_y) = &MapGps2XY ($T_Lat, $T_Lon);
        my ($R_x, $R_y) = &MapGps2XY ($R_Lat, $R_Lon);
        my ($B_x, $B_y) = &MapGps2XY ($B_Lat, $B_Lon);
        my ($L_x, $L_y) = &MapGps2XY ($L_Lat, $L_Lon);

        $map_canvas->createArc ( $L_x, $B_y, $R_x, $T_y,
                                 '-tags' => 'Map-Border-OperatingRadius',
                                 '-extent' => '359',
                                 '-start' => '0',
                                 '-style' => 'chord',
                                 '-outline' => $Cfg->{'mkcockpit'}->{'ColorAirfield'},
                                 '-width' => '1',
                               );
        $map_canvas->raise('Map-Border-OperatingRadius', 'Map');  # Border above Map
        }

    });



#      
# Timer: 0.1s - Map Overlay aktualisieren
#
$frame_map_top->repeat (100, sub
    {

    # Clear old messages from this timer
    &MkMessageInit ("Timer-MapOverlay");

    lock (%MkOsd);              # until end of block
    lock (%MkNcDebug);          # until end of block

    # Aktuell gültige Karte
    %Map = %{$Maps{'Current'}};

    if ( &MkOsdIsValid() )
        {
        # Gueltige OSD Daten

        # Operation Mode
        $OperationMode = "";
        if ( &MkIsWptMode() )         { $OperationMode = "WPT"; }
        if ( $PlayerMode eq "Play" )  { $OperationMode = "Play"; }
        if ( $PlayerMode eq "Pause" ) { $OperationMode = "Paus"; }
        if ( $PlayerMode eq "Home" )  { $OperationMode = "Home"; }
        if ( &MkIsPhMode() )          { $OperationMode = "PH"; }
        if ( &MkIsFreeMode() )        { $OperationMode = "Free"; }

        my $SatsInUse = $MkOsd{'SatsInUse'};
        if ( &CurPosIsValid()  and  &HomePosIsValid() )
            {
            # ausreichender GPS Empfang

            # get x,y map coords of current position
            my ($C_x, $C_y, $C_Angel) = &MapGps2XY($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'}, $MkOsd{'CompassHeading'});
            $MkPos_x = $C_x;
            $MkPos_y = $C_y;

            $System{'CurrPos_x'} = $C_x;
            $System{'CurrPos_y'} = $C_y;

            # rotate MK arrow
            my $dy = sin (deg2rad $C_Angel) * ($MapMkLen/2);
            my $dx = cos (deg2rad $C_Angel) * ($MapMkLen/2);
            my $x0 = $C_x - $dx;
            my $y0 = $C_y - $dy;
            my $x1 = $C_x + $dx;
            my $y1 = $C_y + $dy;
            $map_canvas->coords ('MK-Arrow', $x0, $y0, $x1, $y1);

            # Update speed vector
            my $MapAngel = &MapAngel();   # North to Map-Horizont
            my $GpsSpeedNorth = $MkNcDebug{'Analog_21'};
            my $GpsSpeedEast  = $MkNcDebug{'Analog_22'};
            my $PhiGpsSpeed = rad2deg atan2 ( $GpsSpeedEast, $GpsSpeedNorth );
            $PhiMapSpeed = $PhiGpsSpeed - $MapAngel;

            # 555 cm/s ~ 20 km/h -> Zeigerlänge = $MkSpeedLen bei 20 km/h
            my $dy = sin (deg2rad $PhiMapSpeed) * $MapMkSpeedLen * $MkOsd{'GroundSpeed'} / 555;
            my $dx = cos (deg2rad $PhiMapSpeed) * $MapMkSpeedLen * $MkOsd{'GroundSpeed'} / 555;
            my $x0 = $C_x;
            my $y0 = $C_y;
            my $x1 = $C_x + $dx;
            my $y1 = $C_y + $dy;
            $map_canvas->coords ('MK-Speed', $x0, $y0, $x1, $y1);
 
            # Update Line between Home and MK
            my ($H_x, $H_y) = &MapGps2XY($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'});
            $map_canvas->coords ('MK-Home-Line', $H_x, $H_y, $C_x, $C_y);

            # Update Distance between Home and MK
            my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
                                                    $MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'} );
            my $x = ($C_x - $H_x) / 2 + $H_x + 8;
            my $y = ($C_y - $H_y) / 2 + $H_y + 8;
            $map_canvas->coords ('MK-Home-Dist', $x, $y);
            $map_canvas->itemconfigure ('MK-Home-Dist',
                                        '-text' => sprintf ("%4d m", int ($Dist + 0.5) ),
                                       );
            $System{'HomePos_y'} = $H_x;
            $System{'HomePos_x'} = $H_y;
            $System{'HomeDist'} = $Dist;
            $System{'HomeBearing'} = $Bearing;

            # Update OSD - Sat dependent values
            $map_canvas->itemconfigure ('MK-OSD-Spd-Value', '-text' => sprintf ("%3d km/h", $MkOsd{'GroundSpeed'} * 0.036) );

            # Alt = average Luftdruck und Sat
            my $Alt = int (&Altitude() + 0.5);
            $map_canvas->itemconfigure ('MK-OSD-Alt-Value', '-text' => sprintf ("%3d m", $Alt) );
            $System{'Alt'} = $Alt;

            if ( &TargetIsValid() )
                {
                # Valid Target

                # Update Line between Target and MK
                my ($T_x, $T_y) = &MapGps2XY($MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'});
                $map_canvas->coords ('MK-Target-Line', $C_x, $C_y, $T_x, $T_y);

                # Update Distance between Target and MK
                my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
                                                         $MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'} );
                $System{'TargetPos_x'} = $T_x;
                $System{'TargetPos_y'} = $T_y;
                $System{'TargetDist'} = $Dist;
                $System{'TargetBearing'} = $Bearing;

                if ( $Dist >= 25 )  
                    {
                    my $x = ($C_x - $T_x) / 2 + $T_x - 8;
                    my $y = ($C_y - $T_y) / 2 + $T_y + 8;
                    $map_canvas->coords ('MK-Target-Dist', $x, $y);
                    $map_canvas->itemconfigure ('MK-Target-Dist',
                                                '-text' => sprintf ("%4d m", int ($Dist + 0.5) ),
                                               );
                    }
                else
                    {
                    # Don't show distance < 25m
                    $map_canvas->coords ('MK-Target-Dist', 0, -100);
                    }

                # show target icon
                my $IconHeight = 48;
                my $IconWidth = 48;
                $map_canvas->coords('Target', $T_x - $IconWidth/2, $T_y - $IconHeight );

                $System{'CrossingBorder'} = 0;
                if ( &MkIsFlying()  and  &IsCrossingBorder($MkPos_x, $MkPos_y, $T_x, $T_y) )
                    {
                    # only, if MK is flying
                    $System{'CrossingBorder'} = 1;
                    &MkMessage ($Translate{'MsgCrossingBorder'}, "Timer-MapOverlay");
                    }
                }
            else
                {
                # No valid Target, move target line out of sight/canvas
                $map_canvas->coords ('MK-Target-Line', 0, -100, 0, -100);
                $map_canvas->coords ('MK-Target-Dist', 0, -100);

                # hide target icon
                $map_canvas->coords('Target', 0, -100, );
                }

            # Update Line between MK and POI
            if ( $PoiMode )
                {
                my ($P_x, $P_y) = &MapGps2XY($Poi_Lat, $Poi_Lon);
                $map_canvas->coords ('MK-POI-Line', $P_x, $P_y, $C_x, $C_y);

                $System{'PoiPos_x'} = $P_x;
                $System{'PoiPos_y'} = $P_y;
                }
            else
                {
                $map_canvas->coords ('MK-POI-Line', 0, -200, 0, -200);
                }
            }
        else
            {
            # kein ausreichender Sat-Empfang
            $map_canvas->itemconfigure ('MK-OSD-Spd-Value', '-text' => sprintf ("%3d km/h", 0 ) );
            }

        # Update OSD - non Sat dependent values
        $map_canvas->itemconfigure ('MK-OSD-Odo-Value', '-text' => sprintf ("%3.3f km", $OdoMeter / 1000) );
        $map_canvas->itemconfigure ('MK-OSD-Tim-Value', '-text' => sprintf ("%02d:%02d", $MkFlyingTime / 60, $MkFlyingTime % 60) );
        $map_canvas->itemconfigure ('MK-OSD-Sat-Value', '-text' => $MkOsd{'SatsInUse'} );
        $map_canvas->itemconfigure ('MK-OSD-Cur-Value', '-text' => sprintf ("%.1f A", $MkOsd{'Current'}) );

        # Used Capacity
        my $UsedCapacityFactor = $Cfg->{'map'}->{'UsedCapacityFactor'} || "1.0";
        $System{'UsedCapacity'} = $MkOsd{'UsedCapacity'} * $UsedCapacityFactor;
        $map_canvas->itemconfigure ('MK-OSD-Cap-Value', '-text' => sprintf ("%.2f Ah", $System{'UsedCapacity'} / 1000) );

        # Power
        $System{'Power'} = int ($MkOsd{'Current'} * $MkOsd{'UBat'} + 0.5);
        $map_canvas->itemconfigure ('MK-OSD-Pow-Value', '-text' => sprintf ("%d W", $System{'Power'} ) );

        # battery - OSD and warning
        my $UBat = sprintf ("%3.1f", $MkOsd{'UBat'});
        $System{'UBat'} = $UBat;
        $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-text' => "$UBat V" );

        $System{'BatWarning'} = 0;
        $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-fill' => $Cfg->{'mkcockpit'}->{'ColorOsd'});
        if ( $MkOsd{'UBat'}  <  $Cfg->{'map'}->{'UBatWarning'} )
            {
            if ( time %2 )
                {
                $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-fill' => 'red');
                }

            &MkMessage ($Translate{'MsgBatWarning'}, "Timer-MapOverlay");
            $System{'BatWarning'} = 1;
            }


        # Display Operation Mode
        my $DisplayMode = $OperationMode;
        if ( &MkIsWptMode()  and  $OperationMode eq "Play" )
            {
            my %ModeMatrix =
               (
               "KML-STD" => "Play KML",
               "KML-RND" => "Play KML",
               "KML-MAP" => "Play KML",
               "WPT-STD" => "Play WPT",
               "WPT-RND" => "Rand WPT",
               "WPT-MAP" => "Rand MAP",
               );
            my $Key = "${PlayerWptKmlMode}-${PlayerRandomMode}";
            $DisplayMode = $ModeMatrix{$Key};
            }

        if ( &MkIsWptMode()  and  $OperationMode eq "Paus" )
            {
            $DisplayMode = $DisplayMode . " " . $PlayerPauseMode;
            }

        $System{'RangeWarning'} = 0;
        if ( &MkRangeLimit() )
            {
            $DisplayMode  = "$DisplayMode" . " !!";   # Range Warning
            $System{'RangeWarning'} = 1;
            }
        $map_canvas->itemconfigure ('MK-OSD-Mode-Value', '-text' => $DisplayMode );


        # Waypoints abhaengig vom Modus NC/Player
        my $WpValue = "-- / --";
        if ( $MkOsd{'WaypointNumber'} > 0)
            {
            $WpValue = sprintf ("%d / %d", $MkOsd{'WaypointIndex'}, $MkOsd{'WaypointNumber'});
            }
        if ($PlayerMode ne "Stop" and $PlayerWptKmlMode eq "WPT")
            {
            $WpValue = sprintf ("%d / %d", $WpPlayerIndex +1, scalar @Waypoints);
            }
        if ($PlayerMode ne "Stop" and $PlayerWptKmlMode eq "KML" )
            {
            my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
            my $CurrTime = int ($KmlPlayerIndex * $KmlTimeBase + 0.5);
            my $TotTime = int (scalar @KmlTargets * $KmlTimeBase + 0.5);
            $WpValue = sprintf ("%02d:%02d / %02d:%02d", $CurrTime / 60, $CurrTime % 60, $TotTime / 60, $TotTime % 60);
            }
        $map_canvas->itemconfigure ('MK-OSD-Wp-Value',  '-text' => "$WpValue");

        # Recording Mode
        my $RecordText = "";
        if ( $PlayerRecordMode =~ /REC/i )
            {
            my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
            my $TotTime = int (scalar @KmlTargets * $KmlTimeBase + 0.5);
            $RecordText = sprintf ("Recording %02d:%02d", $TotTime / 60, $TotTime % 60);
            }
        $map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => $RecordText );


        # Farbe MK-Zeiger abhängig vom GPS Empfang
        my $MkCol= $Cfg->{'mkcockpit'}->{'ColorMkSatNo'};
        if ( $SatsInUse >= 1 ) { $MkCol = $Cfg->{'mkcockpit'}->{'ColorMkSatLow'} ; }
        if ( $SatsInUse >= 6 ) { $MkCol = $Cfg->{'mkcockpit'}->{'ColorMkSatGood'}; }
        $map_canvas->itemconfigure ('MK-Arrow', '-fill' => $MkCol);


        # Show/Hide SatFix Icon
        if ($SatsInUse >= 6 )
            {
            $map_canvas->coords('Satellite', $MapSizeX-100, 10, );
            }
        else
            {
            # move icon out of sight
            $map_canvas->coords('Satellite', 0, -100, );
            }


        # Variometer Pointer
        my $dy = -$MkOsd{'Variometer'} * 10;
        $map_canvas->coords('Map-Variometer-Pointer', 5, $MapSizeY/2+$dy, 20, $MapSizeY/2+10+$dy, 20, $MapSizeY/2-10+$dy);

        #
        # System checks
        #

        if ( ! &MkIsMotorOn() )      { &MkMessage ($Translate{'MsgMotorOff'}, "Timer-MapOverlay"); }
        if ( ! &MkIsFlying() )       { &MkMessage ($Translate{'MsgNotFlying'}, "Timer-MapOverlay"); }
        if ( &MkIsCalibrating() )    { &MkMessage ($Translate{'MsgCalibrate'}, "Timer-MapOverlay"); }
        if ( &MkIsMotorStarting() )  { &MkMessage ($Translate{'MsgStart'}, "Timer-MapOverlay") }
        if ( &MkEmergencyLanding() ) { &MkMessage ($Translate{'MsgEmergencyLanding'}, "Timer-MapOverlay"); }
        if ( &MkRangeLimit() )       { &MkMessage ($Translate{'MsgRangeLimit'}, "Timer-MapOverlay"); }

        # RC range check
        my $RcQuality = $MkOsd{'RC_Quality'};
        $System{'RCQuality'} = "";

        if ( $RcQuality < 100 )
            {
            $System{'RCQuality'} = "NO";
            &MkMessage ($Translate{'MsgRcError'}, "Timer-MapOverlay");
            }
        elsif ( $RcQuality < 150 )
            {
            $System{'RCQuality'} = "WEAK";
            &MkMessage ($Translate{'MsgRcWarning'}, "Timer-MapOverlay");
            }

        # Sat reception quality
        if ( $SatsInUse == 0 )
            {
            &MkMessage ($Translate{'MsgNoSatReception'}, "Timer-MapOverlay");
            }
        elsif ( $SatsInUse > 0  and $SatsInUse < 6 )
            {
            &MkMessage ($Translate{'MsgWeakSatReception'}, "Timer-MapOverlay");
            }

        # MK Border check
        $System{'OutsideBorder'} = "0";
        if ( &MkIsFlying() and ! &IsInsideBorder($MkPos_x, $MkPos_y) )
            {
            # only, if MK is flying
            $System{'OutsideBorder'} = "1";
            &MkMessage ($Translate{'MsgOutsideBorder'}, "Timer-MapOverlay");
            }

        # Show Balloon, when aproaching Target
        &TargetMessageShow();

        }
    else
        {
        # keine aktuellen OSD Daten vom MK verfügbar
        &MkMessage ($Translate{'MsgNoData'}, "Timer-MapOverlay");
        }


    # Wp-Number input from keyboard
    $KbTimer++;
    if ( $CbPlayerKey ne "" )
        {
        # Key pressed
        $KbNum = "$KbNum" . "$CbPlayerKey";

        $CbPlayerKey = "";
        $KbTimer = 0;
        }
    if ( $KbTimer > 7  and $KbNum ne "" )
        {
        # number complete, set target
        my $WpIndex = sprintf ("%d", $KbNum);
        &WpTargetSet ($WpIndex - 1);

        # prepare for next number
        $KbNum = "";
        }


    # Show System Messages
    &MkMessageShow();


    # display transmit status in top status line
    my $Color  = 'lightgray';
    my $Relief = 'flat';
    if ( $Cfg->{'serialchannel'}->{'SerialChannelSend'} =~ /y/i )
        {
        $Color = 'green';
        $Relief = 'sunken';
        }
    if ($TxExtOn == 0 )
        {
        $Color = 'red';
        $Relief = 'sunken';
        }
    $map_status_top[0]->configure (-text       => "Tx:$Translate{'serialchannel'}",
                                   -background => $Color,
                                   -relief     => $Relief,
                                  );
    my $Color = 'lightgray';
    my $Relief = 'flat';
    if ( $Cfg->{'externcontrol'}->{'ExternControlSend'} =~ /y/i )
        {
        $Color  = 'green';
        $Relief = 'sunken';
        }
    if ($TxExtOn == 0 )
        {
        $Color  = 'red';
        $Relief = 'sunken';
        }
    $map_status_top[1]->configure (-text       => "Tx:$Translate{'externcontrol'}",
                                   -background => $Color,
                                   -relief     => $Relief,
                                  );

    # display event status in lower status line, max. 10 fields
    my @Event = sort keys %{$Event};
    my $EventIndex = 0;
    while ( $EventIndex < 10 )
        {
        my $Key = pop @Event;
        my $Color = 'lightgray';
        my $Relief = 'flat';

        if ( $Key eq "" )
            {
            # clear unused fields
            $map_status_event[$EventIndex]->configure (-text       => $Key,
                                                       -background => $Color,
                                                       -relief     => $Relief,
                                                      );
            $EventIndex ++;
            }

        elsif ( ref $Event->{$Key} ne ""  and  $Event->{$Key}->{'Active'} =~ /y/i )
            {
            if ( $EventStat{$Key}{'Status'} eq "ON" )
                {
                $Color  = 'green';
                $Relief = 'sunken';
                }
            elsif ( $EventStat{$Key}{'Status'} eq "OFF" )
                {
                $Color  = 'red';
                $Relief = 'sunken';
                }

            $map_status_event[$EventIndex]->configure (-text       => $Key,
                                                       -background => $Color,
                                                       -relief     => $Relief,
                                                      );
            $EventIndex ++;
            }
         else
            {
            # don't display disabled events
            }
        }
    });


#      
# Timer: 0.25s - request Poti LCD-Info Screen 9,10  (8= Sticks)
#
my $LcdScreen = 9;
if ( $Cfg->{'mkcomm'}->{'RequestRcChannel'} =~ /y/i )
    {
    $frame_map_top->repeat (250, sub
        {
        # request only one screen at each timer run to avoid data link overload
        $MkSendQueue->enqueue( "l", "$AddrNC", pack ("C", $LcdScreen) );

        $LcdScreen ++;
        if ( $LcdScreen > 10 )
            {
            $LcdScreen = 9;
            }
        });
    }

#      
# Timer: 0.1s - Tracking Anzeige aktualisieren
#
if ( $Cfg->{'track'}->{'Active'} =~ /y/i )
    {
    $TrackMkCalibrateEdge = 0;  # calibrate edge detection

    $frame_map_top->repeat (100, sub
        {
        # Clear old messages from this timer
        &MkMessageInit ("Timer-Tracking");    

        lock (%MkOsd);              # until end of block
        lock (%MkTrack);            # until end of block

        # Aktuell gültige Karte
        %Map = %{$Maps{'Current'}};

        # Zeiger neu zeichnen
        my $AngelPan  = @ServoPos[$MkTrack{'ServoPan'}];
        my $AngelTilt = @ServoPos[$MkTrack{'ServoTilt'}];
        if ( $AngelPan ne "" and $AngelTilt ne "" )
            {
            my $Angel = $AngelPan;
            if ( $AngelTilt > 90 )
                {
                $Angel += 180;
                }

            # Pan
            my $x0 = $TrackSizeX/2;    
            my $y0 = $TrackSizeY - 0 - $TrackOffY;
            my $x1 = $TrackSizeX/2 - ($TrackPtrLen-22) * cos( deg2rad $Angel);
            my $y1 = $TrackSizeY   - ($TrackPtrLen-22) * sin (deg2rad $Angel) - $TrackOffY;
            $track_canvas->coords ('Track-Ptr-Pan', $x0, $y0, $x1, $y1);

            # Tilt
            my $x0 = $TrackSizeX/2;    
            my $y0 = $TrackSizeY - 0 - $TrackOffY;
            my $x1 = $TrackSizeX/2 - ($TrackPtrLen-22) * cos( deg2rad 180 - $AngelTilt);
            my $y1 = $TrackSizeY   - ($TrackPtrLen-22) * sin (deg2rad 180 - $AngelTilt) - $TrackOffY;
            $track_canvas->coords ('Track-Ptr-Tilt', $x0, $y0, $x1, $y1);
            }

        # Farbe Zeiger abhängig vom GPS Empfang
        my $SatsInUse = $MkOsd{'SatsInUse'};
        my $TrackPtrCol= 'red';
        if ( $SatsInUse >= 1 ) { $TrackPtrCol = 'orange'; }
        if ( $SatsInUse >= 6 ) { $TrackPtrCol = 'green'; }
        $track_canvas->itemconfigure ('Track-Ptr-Pan', '-fill' => $TrackPtrCol);

        # tracker coldstart condition at MK calibration, rising edge of signal
        if ( &MkIsCalibrating() )
            {
            if ( $TrackMkCalibrateEdge == 0 )
                {
                $TrackMkCalibrateEdge = 1;

                # send coldstart command to tracker
                $TrackQueue->enqueue("COLDSTART");
                }
            }
        else
            {
            $TrackMkCalibrateEdge = 0;
            }
        });
    }


#      
# Timer: 0.5s - Waypoint Player
#
$frame_map_top->repeat (500, sub
    {
    # Clear old messages from this timer
    &MkMessageInit ("Timer-Player");        

    lock (%MkOsd);              # until end of block

    if ( &MkIsWptMode() )
        {
        # NC is in WPT Mode

        my $PoiBearing = $Cfg->{'waypoint'}->{'DefaultHeading'};
        if ( &CurPosIsValid()  and  $PoiMode )
            {
            $PoiBearing = &MapGpsTo ( $MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
                                      $Poi_Lat,  $Poi_Lon );
            $System{'PoiBearing'} = $PoiBearing;
            }

        if ( $PlayerMode eq "Pause" )
            {
            if ( $PlayerPause_Lat ne ""  and  $PlayerPause_Lon ne "" )
                {
                # Gespeicherte Pausen-Pos senden
                &MkFlyTo ( '-lat'  => $PlayerPause_Lat,
                           '-lon'  => $PlayerPause_Lon,
                           '-holdtime' => "60",
                           '-heading' => $PoiBearing,
                           '-mode' => "Target",
                         );
                }
            }

        if ( $PlayerMode eq "Home" and &HomePosIsValid() )
            {
            # Gespeicherte oder eingestellte Home-Pos senden

            my $Home_Lat = $Map{'Home_Lat'} || $MkOsd{'HomePos_Lat'};
            my $Home_Lon = $Map{'Home_Lon'} || $MkOsd{'HomePos_Lon'};

            &MkFlyTo ( '-lat'  => $Home_Lat,
                       '-lon'  => $Home_Lon,
                       '-holdtime' => "60",
                       '-heading' => $PoiBearing,
                       '-mode' => "Target",
                     );
            }


        if ( $PlayerWptKmlMode ne 'WPT' )
            {
            # not in Wp mode
            return;
            }


        if ( $PlayerMode eq "Play"  )
            {

            if ( $PlayerRandomMode =~ /RND/i  or $PlayerRandomMode =~ /STD/i )
                {
                my $WpCnt = scalar @Waypoints;
                if ( $WpCnt > 0  and  $WpPlayerIndex < $WpCnt )
                    {
                    # Target WP-Pos senden
                    my $Wp = $Waypoints[$WpPlayerIndex];
                    my $Wp_Lon = $Wp->{'Pos_Lon'};
                    my $Wp_Lat = $Wp->{'Pos_Lat'};
                    if ( $Wp_Lat ne ""  and  $Wp_Lon ne "" )
                        {
                        &MkFlyTo ( '-lat'  => $Wp_Lat,
                                   '-lon'  => $Wp_Lon,
                                   '-holdtime' => "60",
                                   '-heading' => $PoiBearing,
                                   '-mode' => "Target",
                                 );
                        }
                    }
                }

            if ( $PlayerRandomMode =~ /MAP/i )
                {
                # Target Map-Pos senden
                &MkFlyTo ( '-x'  => $RandomTarget_x ,
                           '-y'  => $RandomTarget_y ,
                           '-holdtime' => "60",
                           '-heading' => $PoiBearing,
                           '-mode' => "Target",
                         );
                }
 
            # Ziel erreicht?
            if ( &WpCheckTargetReached() )
                {
                &WpTargetNext();
                }
            }
        }

    # WP Player Holdtime count down
    if ( $WpPlayerHoldtime > 0  )
        {
        $WpPlayerHoldtime --;
        }
    });


#      
# Timer: variabel - KML Player
#
my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
$KmlTimeBase *= 1000;

$frame_map_top->repeat ($KmlTimeBase, sub
    {

    # Clear old messages from this timer
    &MkMessageInit ("Timer-KMLPlayer");    

    lock (%MkOsd);              # until end of block

    if ( &CurPosIsValid() and $PlayerRecordMode =~ /REC/i )
        {
        # record current position
        push @KmlTargets, {
                          'Lat' => $MkOsd{'CurPos_Lat'},
                          'Lon' => $MkOsd{'CurPos_Lon'},
                          'Alt' => $MkOsd{'CurPos_Alt'},
                          };
        }


    if ( &MkIsWptMode() and  $PlayerMode eq "Play"  and  $PlayerWptKmlMode eq 'KML')
        {
        # Play KML

        # Pause, Home is handled in WPT-Timer

        my $KmlCnt = scalar @KmlTargets;
        if ( $KmlCnt > 0  and  $KmlPlayerIndex < $KmlCnt )
            {
            my $Lat = $KmlTargets[$KmlPlayerIndex]->{'Lat'};
            my $Lon = $KmlTargets[$KmlPlayerIndex]->{'Lon'};
            my $Alt = $KmlTargets[$KmlPlayerIndex]->{'Alt'};

            &MkFlyTo ( '-lat'             => $Lat,
                       '-lon'             => $Lon,
                       '-alt'             => $Alt,
                       '-holdtime'        => "60",
                       '-mode'            => "Target",
                     );

            # proceed to next Target
            $KmlPlayerIndex ++;
            if ( $KmlPlayerIndex >= $KmlCnt )
                {
                $KmlPlayerIndex = 0;
                }
            }
        }

    });


#      
# Timer: 1s
#
$frame_map_top->repeat (1000, sub
    {
    # Clear old messages from this timer
    &MkMessageInit ("Timer-Misc-1s");    

    lock (%MkOsd);              # until end of block

    # Aktuell gültige Karte
    %Map = %{$Maps{'Current'}};

    if ( &MkOsdIsValid() )
        {

        # Heartbeat MK Datenübertragung
        if ( time %2 )
            {
            $map_canvas->itemconfigure('Heartbeat', '-image' => 'HeartbeatLarge', );
            }
        else
            {
            $map_canvas->itemconfigure('Heartbeat', '-image' => 'HeartbeatSmall', );
            }

        # Flugzeit aktualisieren
        # Flugzeit selber mitzählen, da $MkOsd{'FlyingTime'} immer 0 (0.14b)
        if ( &MkIsFlying() )
            {
            $MkFlyingTime += 1;
            }

        # Update ODO-Meter
        if ( &CurPosIsValid() )
            {
            my $C_Lat = $MkOsd{'CurPos_Lat'};
            my $C_Lon = $MkOsd{'CurPos_Lon'};

            if ( $OdoFirst ne "" )
                {
                my ($Dist, $Bearing) = &MapGpsTo($C_Lat, $C_Lon, $OdoPos_Lat, $OdoPos_Lon );
                $OdoMeter += $Dist;
                }
            $OdoPos_Lat = $C_Lat;
            $OdoPos_Lon = $C_Lon;
            $OdoFirst = "1";
            }


        # # Estimate remaining flight time
        # my $TextTimeLeft = "";
        # if ( &MkIsFlying()  and  $Capacity <= 90 )
        #     {
        #     my $MaxTime = 100.0 / (100.0 - $Capacity) * $MkFlyingTime;
        #     my $TimeLeft = $MaxTime - $MkFlyingTime;
        #     $TextTimeLeft = sprintf ("(%d min)", int ($TimeLeft / 60.0 + 0.8) );
        #     }
        # $map_canvas->itemconfigure ('MK-OSD-Tim-Left', '-text' => $TextTimeLeft );


        # Footprint
        if ( $Cfg->{'map'}->{'FootprintLength'} > 0 )
            {
            if ( &CurPosIsValid() )
                {
                # neuen Footprint hinten anhaengen
                my ($x, $y) = &MapGps2XY($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'});
                push @Footprint, $x, $y;
                }

            while ( $#Footprint / 2  >  $Cfg->{'map'}->{'FootprintLength'} )
                {
                # alte Footprints entfernen
                splice @Footprint, 0, 2;
                }

            &FootprintRedraw();
            }

        # show tracking antenna icon
        if ( $MkTrack{'HomePos_Lat'} ne ""  and  $MkTrack{'HomePos_Lon'} ne ""  )
            {
            # show antenna icon
            my ($x, $y) = &MapGps2XY($MkTrack{'HomePos_Lat'}, $MkTrack{'HomePos_Lon'});

            my $IconHeight = 48;
            my $IconWidth = 48;
            $map_canvas->coords('Track-Antenna', $x - $IconWidth/2, $y - $IconHeight );
            }
        else
            {
            # move icon out of sight
            $map_canvas->coords('Track-Antenna', 0, -50 );
            }

        if ( $GridIsOn )
            {
            # redraw Grid on canvas
            &GridHide();
            &GridShow();
            }

        # fun :-)
        if ( int rand (100) == 43 )
            {
            &TtsSpeak ('LOW', $Translate{'TtsFun'});
            }
        }

    });


#      
# Timer: 1s - TTS Sprachausgabe
#
$frame_map_top->repeat (1000, sub
    {
    # Aktuell gültige Karte
    %Map = %{$Maps{'Current'}};

    lock (%MkOsd);              # until end of block

    my $StatusInterval = $Cfg->{'tts'}->{'StatusInterval'}  || "30";  
    $SpeechTimer ++;
    my @TtsMsg;

    if ( &MkOsdIsValid() )
        {
        # Gueltige OSD Daten

        if ( $SpeechTimer % $StatusInterval == 0 )
            {
            #
            # give system status, low prio messages
            #

            # clear old low prio messages
            &TtsClear('LOW');

            # get messages from configuration
            for ( $Message = 1; $Message < 10; $Message ++)
                {
                my $MsgId = sprintf ("Message%d", $Message);
                my $Msg = $Cfg->{'tts'}{$MsgId};
   
                if ( $Msg =~ /FLIGHT_TIME/i )
                    {
                    # Flight time
                    my $Min = int ( ($MkFlyingTime +1) / 60);
                    my $Sec = int ( ($MkFlyingTime +1) % 60);
                    my $Text = sprintf ("$Translate{'TtsFlightTimeMinSec'}", $Min, $Sec);
                    if ( $Min == 0 )
                        {
                        $Text = sprintf ("$Translate{'TtsFlightTimeSec'}", $Sec);
                        }
                   &TtsSpeak ('LOW', "$Text");
                   }

                elsif ( $Msg =~ /BATTERY/i )
                    {
                    # Battery
                    my ($Volt, $Tenth) = split '\.', $System{'UBat'};
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsBattery'}", $Volt, $Tenth));
                    }

                elsif ( $Msg =~ /ALTITUDE/i )
                    {
                    # Altitude
                    if ( $System{'Alt'} < 0 )
                        {
                        &TtsSpeak ('LOW', sprintf ("$Translate{'TtsAltitudeNegative'}", abs($System{'Alt'}) ) );
                        }
                    else
                        {
                        &TtsSpeak ('LOW', sprintf ("$Translate{'TtsAltitude'}", $System{'Alt'} ) );
                        }
                    }

                elsif ( $Msg =~ /SATELLITES/i )
                    {
                    # Satellites
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsSatellite'}",  $MkOsd{'SatsInUse'}) );
                    }

                elsif ( $Msg =~ /HOME_DIST/i )
                    {
                    # Home Distance
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsHomeDist'}",  $System{'HomeDist'}) );
                    }

                elsif ( $Msg =~ /TARGET_DIST/i )
                    {
                    # Target Distance
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsTargetDist'}",  $System{'TargetDist'}) );
                    }

                elsif ( $Msg ne "" )
                    {
                    # text or perl code
                    &TtsSpeak ('LOW', eval "$Msg" );
                    }
                }
            }

       # high prio messages
       if ( $SpeechTimer % 5 == 0 )
            {
            if ( $System{'BatWarning'} )          { push @TtsMsg, $Translate{'TtsBatteryWarning'}; }
            if ( $System{'RCQuality'} eq "WEAK" ) { push @TtsMsg, $Translate{'TtsRcWeak'}; }
            if ( $System{'RCQuality'} eq "NO" )   { push @TtsMsg, $Translate{'TtsRcNo'}; }
            if ( $System{'CrossingBorder'} )      { push @TtsMsg, $Translate{'TtsCrossingBorder'}; }
            if ( $System{'OutsideBorder'} )       { push @TtsMsg, $Translate{'TtsOutsideAirfield'}; }
            if ( $System{'RangeWarning'} )        { push @TtsMsg, $Translate{'TtsRange'}; }
            }
        }
    else
        {
        # no data link
        if ( $SpeechTimer % 5 == 0 )
            {
            push @TtsMsg, $Translate{'TtsNoDataLink'};
            }
        }

    # speak high prio messages
    if ( scalar @TtsMsg > 0 )
        {
        # Clear pending messsages
        &TtsClear('HIGH');

        # Speak collected messages
        foreach $Msg (@TtsMsg)
            {
            &TtsSpeak ('HIGH', $Msg);
            }
        }
    });



#
#  Event engine
#

$frame_map_top->repeat (50, sub     # 50ms
    {
    foreach $Key (keys %{$Event})
        {
        if ( ref $Event->{$Key} eq "" )
            {
            # ignore: CreationDate, Version
            next;
            }

        my $Active      = $Event->{$Key}->{'Active'};
        my $Trigger     = $Event->{$Key}->{'Trigger'};       # RISE, FALL, TOGGLE_RISE, TOGGLE_FALL, TRUE, FALSE
        my $Condition   = $Event->{$Key}->{'Condition'};     # Perl commands, Return 1, if Condition is true
        my $Action      = $Event->{$Key}->{'Action'};        # Perl commands
        my $ActionElse  = $Event->{$Key}->{'ActionElse'};    # Perl commands
        my $Delay       = $Event->{$Key}->{'Delay'};         # Action rearm delay in ms
        my $Repeat      = $Event->{$Key}->{'Repeat'};        # Action repeat time in  ms
        my $RepeatElse  = $Event->{$Key}->{'RepeatElse'};    # ActionElse repeat time in ms

        my $DoAction = 0;

        if ( $Active =~ /y/i  and  $Condition ne "" )
            {

            # Provide info about current Event
            %MyEvent = %{$Event->{$Key}};
            $MyEvent{'EventName'} = $Key;

            # lock all shared hashes, which might me used in eval-code
            lock (%MkOsd);           # until end of block
            lock (%MkSerialChannel); # until end of block
            lock (%Stick);           # until end of block
            lock (%MkNcDebug);       # until end of block

            # compile and execute condition at runtime. Return 1, if Condition is true
            my $CondStat = eval "$Condition";    
            if ( $@ ne "" )
                {
                # print compiler message to STDOUT
                print "Event $Key: Condition: $@";
                }

            # current time for Delay, Repeat timing
            my ($t0_s, $t0_us) = gettimeofday;

            # for Condition edge detection
            my $LastCondStat = $EventStat{$Key}{'LastCondValue'};
            $EventStat{$Key}{'LastCondValue'} = $CondStat;

            # some flags
            my $Rise  = (! $LastCondStat  and    $CondStat);
            my $Fall  = (  $LastCondStat  and  ! $CondStat);
            my $True  =   $CondStat;
            my $False = ! $CondStat;

            # Delay time check
            my $DelayCheck = 1;
            my $t1_s  =  $EventStat{$Key}{'DelayTime_s'};
            my $t1_us =  $EventStat{$Key}{'DelayTime_us'};
            if ( $Delay ne "" and  $t1_s ne ""  and  $t1_us ne "" )
                {
                my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
                if ( $Diff_ms < $Delay )
                    {
                    $DelayCheck = 0;
                    }
                }

            #
            # rising or falling edge
            #
            if ( ($Trigger eq "RISE" and  $Rise)  or
                 ($Trigger eq "FALL" and  $Fall) )
                {
                $DoAction = $DelayCheck;

                if ( $DoAction )
                    {
                    $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
                    $EventStat{$Key}{'DelayTime_us'} = $t0_us;

                    # reset Repeat time when event gets active
                    $EventStat{$Key}{'RepeatTime_s'}  = 0;
                    $EventStat{$Key}{'RepeatTime_us'} = 0;

                    # reset event counter
                    $EventStat{$Key}{'EventCnt'} = 0;
                    }
                }

            #
            # toggle on rising/falling edge
            #
            elsif ( $Trigger =~ /TOGGLE/i )
                {
                $DoAction = 0;
                if ( ($Rise  and  $Trigger eq "TOGGLE_RISE")  or
                     ($Fall  and  $Trigger eq "TOGGLE_FALL") )
                    {
                    $DoAction = $DelayCheck;

                    if ( $DoAction )
                        {
                        $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
                        $EventStat{$Key}{'DelayTime_us'} = $t0_us;

                        # reset Repeat time when event gets active
                        $EventStat{$Key}{'RepeatTime_s'}  = 0;
                        $EventStat{$Key}{'RepeatTime_us'} = 0;

                        # reset event counter
                        $EventStat{$Key}{'EventCnt'} = 0;
                        }
                    }

                if ( $DoAction )
                    {
                    $EventStat{$Key}{'ToggleOnOff'} ^= 1;
                    }

                $DoAction = $EventStat{$Key}{'ToggleOnOff'};
                }


            #
            # TRUE Condition
            #
            elsif ( $Trigger eq "TRUE" )
                {
                if ( $True )
                    {
                    $DoAction = $DelayCheck;

                    if ( $DoAction and $Rise)
                        {
                        # reset Repeat time when event gets active
                        $EventStat{$Key}{'RepeatTime_s'}  = 0;
                        $EventStat{$Key}{'RepeatTime_us'} = 0;

                        # reset event counter
                        $EventStat{$Key}{'EventCnt'} = 0;
                        }
                    }

                if ( $Fall )
                    {
                    # set Delay reference from falling edge

                    if ( $DelayCheck )
                        {
                        # timestamp of last falling Condition edge change
                        $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
                        $EventStat{$Key}{'DelayTime_us'} = $t0_us;

                        # reset event counter
                        $EventStat{$Key}{'EventCnt'} = 0;
                        }
                    }
                }

            #
            # FALSE Condition
            #
            elsif ( $Trigger eq "FALSE" )
                {
                if ( $False )
                    {
                    $DoAction = $DelayCheck;

                    if ( $DoAction and $Fall)
                        {
                        # reset Repeat time when event gets active
                        $EventStat{$Key}{'RepeatTime_s'}  = 0;
                        $EventStat{$Key}{'RepeatTime_us'} = 0;

                        # reset event counter
                        $EventStat{$Key}{'EventCnt'} = 0;
                        }
                    }

                if ( $Rise )
                    {
                    # set Delay reference from rising edge

                    if ( $DelayCheck )
                        {
                        # timestamp of last rising Condition edge change
                        $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
                        $EventStat{$Key}{'DelayTime_us'} = $t0_us;

                        # reset event counter
                        $EventStat{$Key}{'EventCnt'} = 0;
                        }
                    }
                }

            else
                {
                # unkown, fall through
                }


            # undefined Status, neither ON, nor OFF
            $EventStat{$Key}{'Status'} = "";

            #
            # Process Action
            #
            if ( $DoAction  and  $Action ne "" )
                {
                $EventStat{$Key}{'Status'} = "ON";
                my $Execute = 1;

                my $t1_s  =  $EventStat{$Key}{'RepeatTime_s'};
                my $t1_us =  $EventStat{$Key}{'RepeatTime_us'};
                if ( $Repeat ne "" and  $t1_s ne ""  and  $t1_us ne "" )
                    {
                    my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
                    if ( $Diff_ms < $Repeat )
                        {
                        $Execute = 0;
                        }
                    }

                if ( $Execute )
                    {
                    # Accounting
                    $MyEvent{'EventCnt'} = $EventStat{$Key}{'EventCnt'} ++;

                    # compile and execute action at runtime
                    eval "$Action";
                    if ( $@ ne "" )
                        {
                        # print compiler message to STDOUT
                        print "Event $Key: Action: $@";
                        }

                    # Timestamp, when Action was executed
                    $EventStat{$Key}{'RepeatTime_s'}  = $t0_s;
                    $EventStat{$Key}{'RepeatTime_us'} = $t0_us;

                    # reset ActionElse repeat timing
                    $EventStat{$Key}{'RepeatTimeElse_s'}  = 0;
                    $EventStat{$Key}{'RepeatTimeElse_us'} = 0;
                    }
                }

            #
            # Process ActionElse
            #
            if (  ! $DoAction  and  $ActionElse ne "" )
                {
                $EventStat{$Key}{'Status'} = "OFF";
                my $Execute = 1;

                # check repeat time
                my $t1_s  =  $EventStat{$Key}{'RepeatTimeElse_s'};
                my $t1_us =  $EventStat{$Key}{'RepeatTimeElse_us'};

                if ( $RepeatElse ne "" and  $t1_s ne ""  and  $t1_us ne "" )
                    {
                    my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
                    if ( $Diff_ms < $RepeatElse )
                        {
                        $Execute = 0;
                        }
                    }

                if ( $Execute )
                    {
                    # Accounting
                    $MyEvent{'EventCnt'} = $EventStat{$Key}{'EventCnt'} ++;

                    # compile and execute action at runtime
                    eval "$ActionElse";
                    if ( $@ ne "" )
                        {
                        # print compiler message to STDOUT
                        print "Event $Key: ActionElse: $@";
                        }

                    # Timestamp, when ActionElse was executed
                    $EventStat{$Key}{'RepeatTimeElse_s'}  = $t0_s;
                    $EventStat{$Key}{'RepeatTimeElse_us'} = $t0_us;
                    }
                }
            }

        else
            {
            $EventStat{$Key}{'Status'} = "DISABLED";
            }
        }
    });


#
# Send Serial Channel + stick button/analog handling
#

my $SerialChannelTiming = $Cfg->{'serialchannel'}->{'SerialChannelFrquency'} || 20;
$SerialChannelTiming = int (1 / $SerialChannelTiming * 1000);
if ( $SerialChannelTiming < 10 )
    {
    $SerialChannelTiming = 10;
    }

# Timer: variable timing
$frame_map_top->repeat ($SerialChannelTiming, sub
    {
    # 12 serial Channel
    for ( my $ChannelInd = 0; $ChannelInd < 12; $ChannelInd ++)
        {
        my $Channel = sprintf ("SerialChannel%02d", $ChannelInd + 1);  # key for Cfg-hash

        my $Control = $Cfg->{'serialchannel'}->{$Channel};
        if ( $Control ne "" )
            {
            my $Expo    = $Cfg->{'serialchannel'}->{$Channel . "Expo"};
            my $Limit   = $Cfg->{'serialchannel'}->{$Channel . "Limit"};
            my $ChannelVal = &ParseControls($Channel, $Control, $Expo, $Limit, $SerialChannelTiming);

            &SerialChannel($ChannelInd, $ChannelVal);
            }
        }

    $MkSerialChannel{'SerialChannelSend'}   = $Cfg->{'serialchannel'}->{'SerialChannelSend'};
    $MkSerialChannel{'SerialChannelTiming'} = $SerialChannelTiming;

    # send serial Channel to MK, nicht wenn deaktiviert  oder WP uebertragen werden
    if ( ! $MkSendWp  and  $Cfg->{'serialchannel'}->{'SerialChannelSend'} =~ /y/i  and  $TxExtOn == 1)
        {
        &SendSerialChannel();  # send values in %MkSerialChannel
        }
    });


#
# Extern Control
#

my $ExternControlTiming = $Cfg->{'externcontrol'}->{'ExternControlFrquency'} || 20;
$ExternControlTiming = int (1 / $ExternControlTiming * 1000);
if ( $ExternControlTiming < 10 )
    {
    $ExternControlTiming = 10;
    }

# Timer: variable timing
$frame_map_top->repeat ($ExternControlTiming, sub
    {
    foreach my $Channel ( qw(ExternControlRoll ExternControlNick ExternControlGier ExternControlGas ExternControlHeight) )
        {
        my $Control = $Cfg->{'externcontrol'}->{$Channel};
        if ( $Control ne "" )
            {
            my $Expo    = $Cfg->{'externcontrol'}->{$Channel . "Expo"};
            my $Limit   = $Cfg->{'externcontrol'}->{$Channel . "Limit"};
            $MkExternControl{$Channel} = &ParseControls($Channel, $Control, $Expo, $Limit, $ExternControlTiming);
            }
        }

    $MkExternControl{'ExternControlSend'}   = $Cfg->{'externcontrol'}->{'ExternControlSend'};
    $MkExternControl{'ExternControlTimimg'} = $ExternControlTiming;

    # send extern control to MK, nicht wenn deaktiviert oder WP uebertragen werden
    if ( ! $MkSendWp  and  $Cfg->{'externcontrol'}->{'ExternControlSend'} =~ /y/i  and  $TxExtOn == 1 )
        {
        &SendExternalControl ( '-nick'   => $MkExternControl{'ExternControlNick'},
                               '-roll'   => $MkExternControl{'ExternControlRoll'},
                               '-gier'   => $MkExternControl{'ExternControlGier'},
                               '-gas'    => $MkExternControl{'ExternControlGas'},
                               '-height' => $MkExternControl{'ExternControlHeight'},
                               '-remotebuttons' => 0,
                               '-config' => 1,
                               '-frame'  => 1,
                             );

        $MkExternControl{'_Timestamp'} = time;   # time when command was sent
        }
    });


#
# mouse/joystick crosshair move in player pause mode
#

$frame_map_top->repeat (50, sub     # 50ms
    {
    if ( $PlayerMode eq 'Pause'  and
         $PlayerPause_Lat ne ""  and  $PlayerPause_Lon ne "" )
        {
        lock (%MkOsd);           # until end of block

        my $ControlX = $Cfg->{'map'}->{'CrosshairMoveX'};
        my $ControlY = $Cfg->{'map'}->{'CrosshairMoveY'};

        if ( $ControlX ne ""  and $ControlY ne "" )
            {
            my $StickRange = 250;
            my $Bias = $StickRange /2;

            my $ExpoX    = $Cfg->{'map'}->{'CrosshairMoveXExpo'};
            my $LimitX   = $Cfg->{'map'}->{'CrosshairMoveXLimit'};
            my $SpeedX  = &ParseControls('CrosshairMoveX', $ControlX, $ExpoX, $LimitX, 50);

            my $ExpoY    = $Cfg->{'map'}->{'CrosshairMoveYExpo'};
            my $LimitY   = $Cfg->{'map'}->{'CrosshairMoveYLimit'};
            my $SpeedY  = &ParseControls('CrosshairMoveY', $ControlY, $ExpoY, $LimitY, 50);

            my $BearingTop = &MapAngel() - 90.0;
            my $BearingMouse = rad2deg atan2($SpeedX, $SpeedY);
            my $Bearing = $BearingTop + $BearingMouse;

            if ( $PlayerPauseMode eq "MK" )
                {
                # MK Reference
                $Bearing = $MkOsd{'CompassHeading'} + $BearingMouse;
                }

            # max. 30m/s at 50ms frame time
            my $Speed = sqrt ($SpeedX * $SpeedX + $SpeedY * $SpeedY);
            my $Dist = 40*50/1000 * $Speed / $Bias;
            ($PlayerPause_Lat, $PlayerPause_Lon) = &MapGpsAt($PlayerPause_Lat, $PlayerPause_Lon, $Dist, $Bearing);

            if ( $SpeedX != 0  or  $SpeedY != 0 )
                {
                $CrosshairTimerCnt = 0;
                }
            }

        #
        # show/update Crosshair for 5 sec after move
        #

        if ( $CrosshairTimerCnt < 5 * 1000/50)
           {
           &CrosshairShow ($PlayerPause_Lat, $PlayerPause_Lon);
           }
        elsif ( $CrosshairTimerCnt ==  5 * 1000/50)
           {
           &CrosshairHide();
           }

        $CrosshairTimerCnt ++;
        }
 
    });


__END__