Subversion Repositories Projects

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
727 rain-er 1
#!/usr/bin/perl
2
#!/usr/bin/perl -d:ptkdb
3
 
4
###############################################################################
5
#
6
# libmktime.pl -  MK Mission Cockpit - Timer for GUI Frontend
7
#
8
# Copyright (C) 2009  Rainer Walther  (rainerwalther-mail@web.de)
9
#
10
# Creative Commons Lizenz mit den Zusaetzen (by, nc, sa)
11
#
12
# Es ist Ihnen gestattet: 
13
#     * das Werk vervielfältigen, verbreiten und öffentlich zugänglich machen
14
#     * Abwandlungen bzw. Bearbeitungen des Inhaltes anfertigen
15
# 
16
# Zu den folgenden Bedingungen:
17
#     * Namensnennung.
18
#       Sie müssen den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen.
19
#     * Keine kommerzielle Nutzung.
20
#       Dieses Werk darf nicht für kommerzielle Zwecke verwendet werden.
21
#     * Weitergabe unter gleichen Bedingungen.
22
#       Wenn Sie den lizenzierten Inhalt bearbeiten oder in anderer Weise umgestalten,
23
#       verändern oder als Grundlage für einen anderen Inhalt verwenden,
24
#       dürfen Sie den neu entstandenen Inhalt nur unter Verwendung von Lizenzbedingungen
25
#       weitergeben, die mit denen dieses Lizenzvertrages identisch oder vergleichbar sind.
26
# 
27
# Im Falle einer Verbreitung müssen Sie anderen die Lizenzbedingungen, unter welche dieses
28
# Werk fällt, mitteilen. Am Einfachsten ist es, einen Link auf diese Seite einzubinden.
29
# 
30
# Jede der vorgenannten Bedingungen kann aufgehoben werden, sofern Sie die Einwilligung
31
# des Rechteinhabers dazu erhalten.
32
# 
33
# Diese Lizenz lässt die Urheberpersönlichkeitsrechte unberührt.
34
# 
35
# Weitere Details zur Lizenzbestimmung gibt es hier:
36
#   Kurzform: http://creativecommons.org/licenses/by-nc-sa/3.0/de/
37
#   Komplett: http://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode
38
#
39
###############################################################################
40
# 2009-08-09 0.2.5 rw Timer moved from mkcockpit.pl
41
#                     Optional Player home-pos in map configuration
42
# 2009-08-23 0.2.6 rw Tracking-Antenna Icon
43
#                     POI heading control
44
# 2009-10-11 0.2.7 rw Tracker control changed
45
# 2010-02-10 0.4.0 rw Altitude average changed
46
#                     Event Engine timer
47
#                     Serial channel timer
48
#                     External Control rimer
49
#                     Crosshair Timer
50
#                     5s timer changed to 3s
51
#                     Current, UsedCapacity, Power added
52
#
53
###############################################################################
54
 
55
$Version{'libmktimer.pl'}  = "0.4.0 - 2010-02-10";
56
 
57
use Math::Trig;
58
use Time::HiRes qw(gettimeofday);   # http://search.cpan.org/~jhi/Time-HiRes-1.9719/HiRes.pm
59
 
60
#
61
# Timer: 3s
62
#
63
$main->repeat (3000, sub
64
    {
65
    if ( ! $MkSendWp )
66
        {
67
        # Abfragefrequenz OSD und Debug regelmäßig neu einstellen, falls Übertragungsfehler
68
        $MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 10) );   # Frequenz OSD Datensatz, * 10ms
69
        $MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 10) );   # Frequenz MK Debug Datensatz, * 10ms
70
        $MkSendQueue->enqueue( "v", "$AddrNC", "");   # Version
71
        $MkSendQueue->enqueue( "e", "$AddrNC", "");   # Error Text Request
72
        }
73
 
74
    lock (%MkOsd);              # until end of block
75
 
76
    # Draw Operation Radius Border
77
    $map_canvas->delete('Map-Border-OperatingRadius');
78
    if ( &HomePosIsValid() )
79
        {
80
        my $Radius = $MkOsd{'OperatingRadius'};
81
        my $H_Lat = $MkOsd{'HomePos_Lat'};
82
        my $H_Lon = $MkOsd{'HomePos_Lon'};
83
        my $Angel = &MapAngel();
84
 
85
        my ($T_Lat, $T_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel -90);
86
        my ($R_Lat, $R_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel);
87
        my ($B_Lat, $B_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel + 90);
88
        my ($L_Lat, $L_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel + 180);
89
 
90
        my ($T_x, $T_y) = &MapGps2XY ($T_Lat, $T_Lon);
91
        my ($R_x, $R_y) = &MapGps2XY ($R_Lat, $R_Lon);
92
        my ($B_x, $B_y) = &MapGps2XY ($B_Lat, $B_Lon);
93
        my ($L_x, $L_y) = &MapGps2XY ($L_Lat, $L_Lon);
94
 
95
        $map_canvas->createArc ( $L_x, $B_y, $R_x, $T_y,
96
                                 '-tags' => 'Map-Border-OperatingRadius',
97
                                 '-extent' => '359',
98
                                 '-start' => '0',
99
                                 '-style' => 'chord',
100
                                 '-outline' => $Cfg->{'mkcockpit'}->{'ColorAirfield'},
101
                                 '-width' => '1',
102
                               );
103
        $map_canvas->raise('Map-Border-OperatingRadius', 'Map');  # Border above Map
104
        }
105
 
106
    });
107
 
108
 
109
 
110
#       
111
# Timer: 0.1s - Map Overlay aktualisieren
112
#
113
$frame_map_top->repeat (100, sub
114
    {
115
 
116
    # Clear old messages from this timer
117
    &MkMessageInit ("Timer-MapOverlay");
118
 
119
    lock (%MkOsd);              # until end of block
120
    lock (%MkNcDebug);          # until end of block
121
 
122
    # Aktuell gültige Karte
123
    %Map = %{$Maps{'Current'}};
124
 
125
    if ( &MkOsdIsValid() )
126
        {
127
        # Gueltige OSD Daten
128
 
129
        # Operation Mode
130
        $OperationMode = "";
131
        if ( &MkIsWptMode() )         { $OperationMode = "WPT"; }
132
        if ( $PlayerMode eq "Play" )  { $OperationMode = "Play"; }
133
        if ( $PlayerMode eq "Pause" ) { $OperationMode = "Paus"; }
134
        if ( $PlayerMode eq "Home" )  { $OperationMode = "Home"; }
135
        if ( &MkIsPhMode() )          { $OperationMode = "PH"; }
136
        if ( &MkIsFreeMode() )        { $OperationMode = "Free"; }
137
 
138
        my $SatsInUse = $MkOsd{'SatsInUse'};
139
        if ( &CurPosIsValid()  and  &HomePosIsValid() )
140
            {
141
            # ausreichender GPS Empfang
142
 
143
            # get x,y map coords of current position
144
            my ($C_x, $C_y, $C_Angel) = &MapGps2XY($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'}, $MkOsd{'CompassHeading'});
145
            $MkPos_x = $C_x;
146
            $MkPos_y = $C_y;
147
 
148
            $System{'CurrPos_x'} = $C_x;
149
            $System{'CurrPos_y'} = $C_y;
150
 
151
            # rotate MK arrow
152
            my $dy = sin (deg2rad $C_Angel) * ($MapMkLen/2);
153
            my $dx = cos (deg2rad $C_Angel) * ($MapMkLen/2);
154
            my $x0 = $C_x - $dx;
155
            my $y0 = $C_y - $dy;
156
            my $x1 = $C_x + $dx;
157
            my $y1 = $C_y + $dy;
158
            $map_canvas->coords ('MK-Arrow', $x0, $y0, $x1, $y1);
159
 
160
            # Update speed vector
161
            my $MapAngel = &MapAngel();   # North to Map-Horizont
162
            my $GpsSpeedNorth = $MkNcDebug{'Analog_21'};
163
            my $GpsSpeedEast  = $MkNcDebug{'Analog_22'};
164
            my $PhiGpsSpeed = rad2deg atan2 ( $GpsSpeedEast, $GpsSpeedNorth );
165
            $PhiMapSpeed = $PhiGpsSpeed - $MapAngel;
166
 
167
            # 555 cm/s ~ 20 km/h -> Zeigerlänge = $MkSpeedLen bei 20 km/h
168
            my $dy = sin (deg2rad $PhiMapSpeed) * $MapMkSpeedLen * $MkOsd{'GroundSpeed'} / 555;
169
            my $dx = cos (deg2rad $PhiMapSpeed) * $MapMkSpeedLen * $MkOsd{'GroundSpeed'} / 555;
170
            my $x0 = $C_x;
171
            my $y0 = $C_y;
172
            my $x1 = $C_x + $dx;
173
            my $y1 = $C_y + $dy;
174
            $map_canvas->coords ('MK-Speed', $x0, $y0, $x1, $y1);
175
 
176
            # Update Line between Home and MK
177
            my ($H_x, $H_y) = &MapGps2XY($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'});
178
            $map_canvas->coords ('MK-Home-Line', $H_x, $H_y, $C_x, $C_y);
179
 
180
            # Update Distance between Home and MK
181
            my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
182
                                                    $MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'} );
183
            my $x = ($C_x - $H_x) / 2 + $H_x + 8;
184
            my $y = ($C_y - $H_y) / 2 + $H_y + 8;
185
            $map_canvas->coords ('MK-Home-Dist', $x, $y);
186
            $map_canvas->itemconfigure ('MK-Home-Dist',
187
                                        '-text' => sprintf ("%4d m", int ($Dist + 0.5) ),
188
                                       );
189
            $System{'HomePos_y'} = $H_x;
190
            $System{'HomePos_x'} = $H_y;
191
            $System{'HomeDist'} = $Dist;
192
            $System{'HomeBearing'} = $Bearing;
193
 
194
            # Update OSD - Sat dependent values
195
            $map_canvas->itemconfigure ('MK-OSD-Spd-Value', '-text' => sprintf ("%3d km/h", $MkOsd{'GroundSpeed'} * 0.036) );
196
 
197
            # Alt = average Luftdruck und Sat
198
            my $Alt = int (&Altitude() + 0.5);
199
            $map_canvas->itemconfigure ('MK-OSD-Alt-Value', '-text' => sprintf ("%3d m", $Alt) );
200
            $System{'Alt'} = $Alt;
201
 
202
            if ( &TargetIsValid() )
203
                {
204
                # Valid Target
205
 
206
                # Update Line between Target and MK
207
                my ($T_x, $T_y) = &MapGps2XY($MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'});
208
                $map_canvas->coords ('MK-Target-Line', $C_x, $C_y, $T_x, $T_y);
209
 
210
                # Update Distance between Target and MK
211
                my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
212
                                                         $MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'} );
213
                $System{'TargetPos_x'} = $T_x;
214
                $System{'TargetPos_y'} = $T_y;
215
                $System{'TargetDist'} = $Dist;
216
                $System{'TargetBearing'} = $Bearing;
217
 
218
                if ( $Dist >= 25 )  
219
                    {
220
                    my $x = ($C_x - $T_x) / 2 + $T_x - 8;
221
                    my $y = ($C_y - $T_y) / 2 + $T_y + 8;
222
                    $map_canvas->coords ('MK-Target-Dist', $x, $y);
223
                    $map_canvas->itemconfigure ('MK-Target-Dist',
224
                                                '-text' => sprintf ("%4d m", int ($Dist + 0.5) ),
225
                                               );
226
                    }
227
                else
228
                    {
229
                    # Don't show distance < 25m
230
                    $map_canvas->coords ('MK-Target-Dist', 0, -100);
231
                    }
232
 
233
                # show target icon
234
                my $IconHeight = 48;
235
                my $IconWidth = 48;
236
                $map_canvas->coords('Target', $T_x - $IconWidth/2, $T_y - $IconHeight );
237
 
238
                $System{'CrossingBorder'} = 0;
239
                if ( &MkIsFlying()  and  &IsCrossingBorder($MkPos_x, $MkPos_y, $T_x, $T_y) )
240
                    {
241
                    # only, if MK is flying
242
                    $System{'CrossingBorder'} = 1;
243
                    &MkMessage ($Translate{'MsgCrossingBorder'}, "Timer-MapOverlay");
244
                    }
245
                }
246
            else
247
                {
248
                # No valid Target, move target line out of sight/canvas
249
                $map_canvas->coords ('MK-Target-Line', 0, -100, 0, -100);
250
                $map_canvas->coords ('MK-Target-Dist', 0, -100);
251
 
252
                # hide target icon
253
                $map_canvas->coords('Target', 0, -100, );
254
                }
255
 
256
            # Update Line between MK and POI
257
            if ( $PoiMode )
258
                {
259
                my ($P_x, $P_y) = &MapGps2XY($Poi_Lat, $Poi_Lon);
260
                $map_canvas->coords ('MK-POI-Line', $P_x, $P_y, $C_x, $C_y);
261
 
262
                $System{'PoiPos_x'} = $P_x;
263
                $System{'PoiPos_y'} = $P_y;
264
                }
265
            else
266
                {
267
                $map_canvas->coords ('MK-POI-Line', 0, -200, 0, -200);
268
                }
269
            }
270
        else
271
            {
272
            # kein ausreichender Sat-Empfang
273
            $map_canvas->itemconfigure ('MK-OSD-Spd-Value', '-text' => sprintf ("%3d km/h", 0 ) );
274
            }
275
 
276
        # Update OSD - non Sat dependent values
277
        $map_canvas->itemconfigure ('MK-OSD-Odo-Value', '-text' => sprintf ("%3.3f km", $OdoMeter / 1000) );
278
        $map_canvas->itemconfigure ('MK-OSD-Tim-Value', '-text' => sprintf ("%02d:%02d", $MkFlyingTime / 60, $MkFlyingTime % 60) );
279
        $map_canvas->itemconfigure ('MK-OSD-Sat-Value', '-text' => $MkOsd{'SatsInUse'} );
280
        $map_canvas->itemconfigure ('MK-OSD-Cur-Value', '-text' => sprintf ("%.1f A", $MkOsd{'Current'}) );
281
 
282
        # Used Capacity
283
        my $UsedCapacityFactor = $Cfg->{'map'}->{'UsedCapacityFactor'} || "1.0";
284
        $System{'UsedCapacity'} = $MkOsd{'UsedCapacity'} * $UsedCapacityFactor;
285
        $map_canvas->itemconfigure ('MK-OSD-Cap-Value', '-text' => sprintf ("%.2f Ah", $System{'UsedCapacity'} / 1000) );
286
 
287
        # Power
288
        $System{'Power'} = int ($MkOsd{'Current'} * $MkOsd{'UBat'} + 0.5);
289
        $map_canvas->itemconfigure ('MK-OSD-Pow-Value', '-text' => sprintf ("%d W", $System{'Power'} ) );
290
 
291
        # battery - OSD and warning
292
        my $UBat = sprintf ("%3.1f", $MkOsd{'UBat'});
293
        $System{'UBat'} = $UBat;
294
        $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-text' => "$UBat V" );
295
 
296
        $System{'BatWarning'} = 0;
297
        $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-fill' => $Cfg->{'mkcockpit'}->{'ColorOsd'});
298
        if ( $MkOsd{'UBat'}  <  $Cfg->{'mkcockpit'}->{'UBatWarning'} )
299
            {
300
            if ( time %2 )
301
                {
302
                $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-fill' => 'red');
303
                }
304
 
305
            &MkMessage ($Translate{'MsgBatWarning'}, "Timer-MapOverlay");
306
            $System{'BatWarning'} = 1;
307
            }
308
 
309
 
310
        # Display Operation Mode
311
        my $DisplayMode = $OperationMode;
312
        if ( &MkIsWptMode()  and  $OperationMode eq "Play" )
313
            {
314
            my %ModeMatrix =
315
               (
316
               "KML-STD" => "Play KML",
317
               "KML-RND" => "Play KML",
318
               "KML-MAP" => "Play KML",
319
               "WPT-STD" => "Play WPT",
320
               "WPT-RND" => "Rand WPT",
321
               "WPT-MAP" => "Rand MAP",
322
               );
323
            my $Key = "${PlayerWptKmlMode}-${PlayerRandomMode}";
324
            $DisplayMode = $ModeMatrix{$Key};
325
            }
326
 
327
        if ( &MkIsWptMode()  and  $OperationMode eq "Paus" )
328
            {
329
            $DisplayMode = $DisplayMode . " " . $PlayerPauseMode;
330
            }
331
 
332
        $System{'RangeWarning'} = 0;
333
        if ( &MkRangeLimit() )
334
            {
335
            $DisplayMode  = "$DisplayMode" . " !!";   # Range Warning
336
            $System{'RangeWarning'} = 1;
337
            }
338
        $map_canvas->itemconfigure ('MK-OSD-Mode-Value', '-text' => $DisplayMode );
339
 
340
 
341
        # Waypoints abhaengig vom Modus NC/Player
342
        my $WpValue = "-- / --";
343
        if ( $MkOsd{'WaypointNumber'} > 0)
344
            {
345
            $WpValue = sprintf ("%d / %d", $MkOsd{'WaypointIndex'}, $MkOsd{'WaypointNumber'});
346
            }
347
        if ($PlayerMode ne "Stop" and $PlayerWptKmlMode eq "WPT")
348
            {
349
            $WpValue = sprintf ("%d / %d", $WpPlayerIndex +1, scalar @Waypoints);
350
            }
351
        if ($PlayerMode ne "Stop" and $PlayerWptKmlMode eq "KML" )
352
            {
353
            my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
354
            my $CurrTime = int ($KmlPlayerIndex * $KmlTimeBase + 0.5);
355
            my $TotTime = int (scalar @KmlTargets * $KmlTimeBase + 0.5);
356
            $WpValue = sprintf ("%02d:%02d / %02d:%02d", $CurrTime / 60, $CurrTime % 60, $TotTime / 60, $TotTime % 60);
357
            }
358
        $map_canvas->itemconfigure ('MK-OSD-Wp-Value',  '-text' => "$WpValue");
359
 
360
        # Recording Mode
361
        my $RecordText = "";
362
        if ( $PlayerRecordMode =~ /REC/i )
363
            {
364
            my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
365
            my $TotTime = int (scalar @KmlTargets * $KmlTimeBase + 0.5);
366
            $RecordText = sprintf ("Recording %02d:%02d", $TotTime / 60, $TotTime % 60);
367
            }
368
        $map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => $RecordText );
369
 
370
 
371
        # Farbe MK-Zeiger abhängig vom GPS Empfang
372
        my $MkCol= $Cfg->{'mkcockpit'}->{'ColorMkSatNo'};
373
        if ( $SatsInUse >= 1 ) { $MkCol = $Cfg->{'mkcockpit'}->{'ColorMkSatLow'} ; }
374
        if ( $SatsInUse >= 6 ) { $MkCol = $Cfg->{'mkcockpit'}->{'ColorMkSatGood'}; }
375
        $map_canvas->itemconfigure ('MK-Arrow', '-fill' => $MkCol);
376
 
377
 
378
        # Show/Hide SatFix Icon
379
        if ($SatsInUse >= 6 )
380
            {
381
            $map_canvas->coords('Satellite', $MapSizeX-100, 10, );
382
            }
383
        else
384
            {
385
            # move icon out of sight
386
            $map_canvas->coords('Satellite', 0, -100, );
387
            }
388
 
389
 
390
        # Variometer Pointer
391
        my $dy = -$MkOsd{'Variometer'} * 10;
392
        $map_canvas->coords('Map-Variometer-Pointer', 5, $MapSizeY/2+$dy, 20, $MapSizeY/2+10+$dy, 20, $MapSizeY/2-10+$dy);
393
 
394
        #
395
        # System checks
396
        #
397
 
398
        if ( ! &MkIsMotorOn() )      { &MkMessage ($Translate{'MsgMotorOff'}, "Timer-MapOverlay"); }
399
        if ( ! &MkIsFlying() )       { &MkMessage ($Translate{'MsgNotFlying'}, "Timer-MapOverlay"); }
400
        if ( &MkIsCalibrating() )    { &MkMessage ($Translate{'MsgCalibrate'}, "Timer-MapOverlay"); }
401
        if ( &MkIsMotorStarting() )  { &MkMessage ($Translate{'MsgStart'}, "Timer-MapOverlay") }
402
        if ( &MkEmergencyLanding() ) { &MkMessage ($Translate{'MsgEmergencyLanding'}, "Timer-MapOverlay"); }
403
        if ( &MkRangeLimit() )       { &MkMessage ($Translate{'MsgRangeLimit'}, "Timer-MapOverlay"); }
404
 
405
        # RC range check
406
        my $RcQuality = $MkOsd{'RC_Quality'};
407
        $System{'RCQuality'} = "";
408
 
409
        if ( $RcQuality < 100 )
410
            {
411
            $System{'RCQuality'} = "NO";
412
            &MkMessage ($Translate{'MsgRcError'}, "Timer-MapOverlay");
413
            }
414
        elsif ( $RcQuality < 150 )
415
            {
416
            $System{'RCQuality'} = "WEAK";
417
            &MkMessage ($Translate{'MsgRcWarning'}, "Timer-MapOverlay");
418
            }
419
 
420
        # Sat reception quality
421
        if ( $SatsInUse == 0 )
422
            {
423
            &MkMessage ($Translate{'MsgNoSatReception'}, "Timer-MapOverlay");
424
            }
425
        elsif ( $SatsInUse > 0  and $SatsInUse < 6 )
426
            {
427
            &MkMessage ($Translate{'MsgWeakSatReception'}, "Timer-MapOverlay");
428
            }
429
 
430
        # MK Border check
431
        $System{'OutsideBorder'} = "0";
432
        if ( &MkIsFlying() and ! &IsInsideBorder($MkPos_x, $MkPos_y) )
433
            {
434
            # only, if MK is flying
435
            $System{'OutsideBorder'} = "1";
436
            &MkMessage ($Translate{'MsgOutsideBorder'}, "Timer-MapOverlay");
437
            }
438
 
439
        # Show Balloon, when aproaching Target
440
        &TargetMessageShow();
441
 
442
        }
443
    else
444
        {
445
        # keine aktuellen OSD Daten vom MK verfügbar
446
        &MkMessage ($Translate{'MsgNoData'}, "Timer-MapOverlay");
447
        }
448
 
449
 
450
    # Wp-Number input from keyboard
451
    $KbTimer++;
452
    if ( $CbPlayerKey ne "" )
453
        {
454
        # Key pressed
455
        $KbNum = "$KbNum" . "$CbPlayerKey";
456
 
457
        $CbPlayerKey = "";
458
        $KbTimer = 0;
459
        }
460
    if ( $KbTimer > 7  and $KbNum ne "" )
461
        {
462
        # number complete, set target
463
        my $WpIndex = sprintf ("%d", $KbNum);
464
        &WpTargetSet ($WpIndex - 1);
465
 
466
        # prepare for next number
467
        $KbNum = "";
468
        }
469
 
470
 
471
    # Show System Messages
472
    &MkMessageShow();
473
 
474
 
475
    # display transmit status in top status line
476
    my $Color  = 'lightgray';
477
    my $Relief = 'flat';
478
    if ( $Cfg->{'serialchannel'}->{'SerialChannelSend'} =~ /y/i )
479
        {
480
        $Color = 'green';
481
        $Relief = 'sunken';
482
        }
483
    if ($TxExtOn == 0 )
484
        {
485
        $Color = 'red';
486
        $Relief = 'sunken';
487
        }
488
    $map_status_top[0]->configure (-text       => "Tx:$Translate{'serialchannel'}",
489
                                   -background => $Color,
490
                                   -relief     => $Relief,
491
                                  );
492
    my $Color = 'lightgray';
493
    my $Relief = 'flat';
494
    if ( $Cfg->{'externcontrol'}->{'ExternControlSend'} =~ /y/i )
495
        {
496
        $Color  = 'green';
497
        $Relief = 'sunken';
498
        }
499
    if ($TxExtOn == 0 )
500
        {
501
        $Color  = 'red';
502
        $Relief = 'sunken';
503
        }
504
    $map_status_top[1]->configure (-text       => "Tx:$Translate{'externcontrol'}",
505
                                   -background => $Color,
506
                                   -relief     => $Relief,
507
                                  );
508
 
509
    # display event status in lower status line, max. 10 fields
510
    my @Event = sort keys %{$Event};
511
    my $EventIndex = 0;
512
    while ( $EventIndex < 10 )
513
        {
514
        my $Key = pop @Event;
515
        my $Color = 'lightgray';
516
        my $Relief = 'flat';
517
 
518
        if ( $Key eq "" )
519
            {
520
            # clear unused fields
521
            $map_status_event[$EventIndex]->configure (-text       => $Key,
522
                                                       -background => $Color,
523
                                                       -relief     => $Relief,
524
                                                      );
525
            $EventIndex ++;
526
            }
527
 
528
        elsif ( ref $Event->{$Key} ne ""  and  $Event->{$Key}->{'Active'} =~ /y/i )
529
            {
530
            if ( $EventStat{$Key}{'Status'} eq "ON" )
531
                {
532
                $Color  = 'green';
533
                $Relief = 'sunken';
534
                }
535
            elsif ( $EventStat{$Key}{'Status'} eq "OFF" )
536
                {
537
                $Color  = 'red';
538
                $Relief = 'sunken';
539
                }
540
 
541
            $map_status_event[$EventIndex]->configure (-text       => $Key,
542
                                                       -background => $Color,
543
                                                       -relief     => $Relief,
544
                                                      );
545
            $EventIndex ++;
546
            }
547
         else
548
            {
549
            # don't display disabled events
550
            }
551
        }
552
 
553
    });
554
 
555
#       
556
# Timer: 0.1s - Tracking Anzeige aktualisieren
557
#
558
if ( $Cfg->{'track'}->{'Active'} =~ /y/i )
559
    {
560
    $TrackMkCalibrateEdge = 0;  # calibrate edge detection
561
 
562
    $frame_map_top->repeat (100, sub
563
        {
564
        # Clear old messages from this timer
565
        &MkMessageInit ("Timer-Tracking");    
566
 
567
        lock (%MkOsd);              # until end of block
568
        lock (%MkTrack);            # until end of block
569
 
570
        # Aktuell gültige Karte
571
        %Map = %{$Maps{'Current'}};
572
 
573
        # Zeiger neu zeichnen
574
        my $AngelPan  = @ServoPos[$MkTrack{'ServoPan'}];
575
        my $AngelTilt = @ServoPos[$MkTrack{'ServoTilt'}];
576
        if ( $AngelPan ne "" and $AngelTilt ne "" )
577
            {
578
            my $Angel = $AngelPan;
579
            if ( $AngelTilt > 90 )
580
                {
581
                $Angel += 180;
582
                }
583
 
584
            # Pan
585
            my $x0 = $TrackSizeX/2;    
586
            my $y0 = $TrackSizeY - 0 - $TrackOffY;
587
            my $x1 = $TrackSizeX/2 - ($TrackPtrLen-22) * cos( deg2rad $Angel);
588
            my $y1 = $TrackSizeY   - ($TrackPtrLen-22) * sin (deg2rad $Angel) - $TrackOffY;
589
            $track_canvas->coords ('Track-Ptr-Pan', $x0, $y0, $x1, $y1);
590
 
591
            # Tilt
592
            my $x0 = $TrackSizeX/2;    
593
            my $y0 = $TrackSizeY - 0 - $TrackOffY;
594
            my $x1 = $TrackSizeX/2 - ($TrackPtrLen-22) * cos( deg2rad 180 - $AngelTilt);
595
            my $y1 = $TrackSizeY   - ($TrackPtrLen-22) * sin (deg2rad 180 - $AngelTilt) - $TrackOffY;
596
            $track_canvas->coords ('Track-Ptr-Tilt', $x0, $y0, $x1, $y1);
597
            }
598
 
599
        # Farbe Zeiger abhängig vom GPS Empfang
600
        my $SatsInUse = $MkOsd{'SatsInUse'};
601
        my $TrackPtrCol= 'red';
602
        if ( $SatsInUse >= 1 ) { $TrackPtrCol = 'orange'; }
603
        if ( $SatsInUse >= 6 ) { $TrackPtrCol = 'green'; }
604
        $track_canvas->itemconfigure ('Track-Ptr-Pan', '-fill' => $TrackPtrCol);
605
 
606
        # tracker coldstart condition at MK calibration, rising edge of signal
607
        if ( &MkIsCalibrating() )
608
            {
609
            if ( $TrackMkCalibrateEdge == 0 )
610
                {
611
                $TrackMkCalibrateEdge = 1;
612
 
613
                # send coldstart command to tracker
614
                $TrackQueue->enqueue("COLDSTART");
615
                }
616
            }
617
        else
618
            {
619
            $TrackMkCalibrateEdge = 0;
620
            }
621
        });
622
    }
623
 
624
 
625
#       
626
# Timer: 0.5s - Waypoint Player
627
#
628
$frame_map_top->repeat (500, sub
629
    {
630
    # Clear old messages from this timer
631
    &MkMessageInit ("Timer-Player");        
632
 
633
    lock (%MkOsd);              # until end of block
634
 
635
    if ( &MkIsWptMode() )
636
        {
637
        # NC is in WPT Mode 
638
 
639
        my $PoiBearing = $Cfg->{'waypoint'}->{'DefaultHeading'};
640
        if ( &CurPosIsValid()  and  $PoiMode )
641
            {
642
            $PoiBearing = &MapGpsTo ( $MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
643
                                      $Poi_Lat,  $Poi_Lon );
644
            $System{'PoiBearing'} = $PoiBearing;
645
            }
646
 
647
        if ( $PlayerMode eq "Pause" )
648
            {
649
            if ( $PlayerPause_Lat ne ""  and  $PlayerPause_Lon ne "" )
650
                {
651
                # Gespeicherte Pausen-Pos senden
652
                &MkFlyTo ( '-lat'  => $PlayerPause_Lat,
653
                           '-lon'  => $PlayerPause_Lon,
654
                           '-holdtime' => "60",
655
                           '-heading' => $PoiBearing,
656
                           '-mode' => "Target",
657
                         );
658
                }
659
            }
660
 
661
        if ( $PlayerMode eq "Home" and &HomePosIsValid() )
662
            {
663
            # Gespeicherte oder eingestellte Home-Pos senden
664
 
665
            my $Home_Lat = $Map{'Home_Lat'} || $MkOsd{'HomePos_Lat'};
666
            my $Home_Lon = $Map{'Home_Lon'} || $MkOsd{'HomePos_Lon'};
667
 
668
            &MkFlyTo ( '-lat'  => $Home_Lat,
669
                       '-lon'  => $Home_Lon,
670
                       '-holdtime' => "60",
671
                       '-heading' => $PoiBearing,
672
                       '-mode' => "Target",
673
                     );
674
            }
675
 
676
 
677
        if ( $PlayerWptKmlMode ne 'WPT' )
678
            {
679
            # not in Wp mode
680
            return;
681
            }
682
 
683
 
684
        if ( $PlayerMode eq "Play"  )
685
            {
686
 
687
            if ( $PlayerRandomMode =~ /RND/i  or $PlayerRandomMode =~ /STD/i )
688
                {
689
                my $WpCnt = scalar @Waypoints;
690
                if ( $WpCnt > 0  and  $WpPlayerIndex < $WpCnt )
691
                    {
692
                    # Target WP-Pos senden
693
                    my $Wp = $Waypoints[$WpPlayerIndex];
694
                    my $Wp_Lon = $Wp->{'Pos_Lon'};
695
                    my $Wp_Lat = $Wp->{'Pos_Lat'};
696
                    if ( $Wp_Lat ne ""  and  $Wp_Lon ne "" )
697
                        {
698
                        &MkFlyTo ( '-lat'  => $Wp_Lat,
699
                                   '-lon'  => $Wp_Lon,
700
                                   '-holdtime' => "60",
701
                                   '-heading' => $PoiBearing,
702
                                   '-mode' => "Target",
703
                                 );
704
                        }
705
                    }
706
                }
707
 
708
            if ( $PlayerRandomMode =~ /MAP/i )
709
                {
710
                # Target Map-Pos senden
711
                &MkFlyTo ( '-x'  => $RandomTarget_x ,
712
                           '-y'  => $RandomTarget_y ,
713
                           '-holdtime' => "60",
714
                           '-heading' => $PoiBearing,
715
                           '-mode' => "Target",
716
                         );
717
                }
718
 
719
            # Ziel erreicht?
720
            if ( &WpCheckTargetReached() )
721
                {
722
                &WpTargetNext();
723
                }
724
            }
725
        }
726
 
727
    # WP Player Holdtime count down
728
    if ( $WpPlayerHoldtime > 0  )
729
        {
730
        $WpPlayerHoldtime --;
731
        }
732
    });
733
 
734
 
735
#       
736
# Timer: variabel - KML Player
737
#
738
my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
739
$KmlTimeBase *= 1000;
740
 
741
$frame_map_top->repeat ($KmlTimeBase, sub
742
    {
743
 
744
    # Clear old messages from this timer
745
    &MkMessageInit ("Timer-KMLPlayer");    
746
 
747
    lock (%MkOsd);              # until end of block
748
 
749
    if ( &CurPosIsValid() and $PlayerRecordMode =~ /REC/i )
750
        {
751
        # record current position
752
        push @KmlTargets, {
753
                          'Lat' => $MkOsd{'CurPos_Lat'},
754
                          'Lon' => $MkOsd{'CurPos_Lon'},
755
                          'Alt' => $MkOsd{'CurPos_Alt'},
756
                          };
757
        }
758
 
759
 
760
    if ( &MkIsWptMode() and  $PlayerMode eq "Play"  and  $PlayerWptKmlMode eq 'KML')
761
        {
762
        # Play KML
763
 
764
        # Pause, Home is handled in WPT-Timer
765
 
766
        my $KmlCnt = scalar @KmlTargets;
767
        if ( $KmlCnt > 0  and  $KmlPlayerIndex < $KmlCnt )
768
            {
769
            my $Lat = $KmlTargets[$KmlPlayerIndex]->{'Lat'};
770
            my $Lon = $KmlTargets[$KmlPlayerIndex]->{'Lon'};
771
            my $Alt = $KmlTargets[$KmlPlayerIndex]->{'Alt'};
772
 
773
            &MkFlyTo ( '-lat'             => $Lat,
774
                       '-lon'             => $Lon,
775
                       '-alt'             => $Alt,
776
                       '-holdtime'        => "60",
777
                       '-mode'            => "Target",
778
                     );
779
 
780
            # proceed to next Target
781
            $KmlPlayerIndex ++;
782
            if ( $KmlPlayerIndex >= $KmlCnt )
783
                {
784
                $KmlPlayerIndex = 0;
785
                }
786
            }
787
        }
788
 
789
    });
790
 
791
 
792
#       
793
# Timer: 1s
794
#
795
$frame_map_top->repeat (1000, sub
796
    {
797
    # Clear old messages from this timer
798
    &MkMessageInit ("Timer-Misc-1s");    
799
 
800
    lock (%MkOsd);              # until end of block
801
 
802
    # Aktuell gültige Karte
803
    %Map = %{$Maps{'Current'}};
804
 
805
    if ( &MkOsdIsValid() )
806
        {
807
 
808
        # Heartbeat MK Datenübertragung
809
        if ( time %2 )
810
            {
811
            $map_canvas->itemconfigure('Heartbeat', '-image' => 'HeartbeatLarge', );
812
            }
813
        else
814
            {
815
            $map_canvas->itemconfigure('Heartbeat', '-image' => 'HeartbeatSmall', );
816
            }
817
 
818
        # Flugzeit aktualisieren
819
        # Flugzeit selber mitzählen, da $MkOsd{'FlyingTime'} immer 0 (0.14b)
820
        if ( &MkIsFlying() )
821
            {
822
            $MkFlyingTime += 1;
823
            }
824
 
825
        # Update ODO-Meter
826
        if ( &CurPosIsValid() )
827
            {
828
            my $C_Lat = $MkOsd{'CurPos_Lat'};
829
            my $C_Lon = $MkOsd{'CurPos_Lon'};
830
 
831
            if ( $OdoFirst ne "" )
832
                {
833
                my ($Dist, $Bearing) = &MapGpsTo($C_Lat, $C_Lon, $OdoPos_Lat, $OdoPos_Lon );
834
                $OdoMeter += $Dist;
835
                }
836
            $OdoPos_Lat = $C_Lat;
837
            $OdoPos_Lon = $C_Lon;
838
            $OdoFirst = "1";
839
            }
840
 
841
 
842
        # # Estimate remaining flight time
843
        # my $TextTimeLeft = "";
844
        # if ( &MkIsFlying()  and  $Capacity <= 90 )
845
        #     {
846
        #     my $MaxTime = 100.0 / (100.0 - $Capacity) * $MkFlyingTime;
847
        #     my $TimeLeft = $MaxTime - $MkFlyingTime;
848
        #     $TextTimeLeft = sprintf ("(%d min)", int ($TimeLeft / 60.0 + 0.8) );
849
        #     }
850
        # $map_canvas->itemconfigure ('MK-OSD-Tim-Left', '-text' => $TextTimeLeft );
851
 
852
 
853
        # Footprint
854
        if ( $Cfg->{'mkcockpit'}->{'FootprintLength'} > 0 )
855
            {
856
            if ( &CurPosIsValid() )
857
                {
858
                # neuen Footprint hinten anhaengen
859
                my ($x, $y) = &MapGps2XY($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'});
860
                push @Footprint, $x, $y;
861
                }
862
 
863
            while ( $#Footprint / 2  >  $Cfg->{'mkcockpit'}->{'FootprintLength'} )
864
                {
865
                # alte Footprints entfernen
866
                splice @Footprint, 0, 2;
867
                }
868
 
869
            &FootprintRedraw();
870
            }
871
 
872
        # show tracking antenna icon
873
        if ( $MkTrack{'HomePos_Lat'} ne ""  and  $MkTrack{'HomePos_Lon'} ne ""  )
874
            {
875
            # show antenna icon
876
            my ($x, $y) = &MapGps2XY($MkTrack{'HomePos_Lat'}, $MkTrack{'HomePos_Lon'});
877
 
878
            my $IconHeight = 48;
879
            my $IconWidth = 48;
880
            $map_canvas->coords('Track-Antenna', $x - $IconWidth/2, $y - $IconHeight );
881
            }
882
        else
883
            {
884
            # move icon out of sight
885
            $map_canvas->coords('Track-Antenna', 0, -50 );
886
            }
887
 
888
        if ( $GridIsOn )
889
            {
890
            # redraw Grid on canvas
891
            &GridHide();
892
            &GridShow();
893
            }
894
 
895
        # fun :-)
896
        if ( int rand (100) == 43 )
897
            {
898
            &TtsSpeak ('LOW', $Translate{'TtsFun'});
899
            }
900
        }
901
 
902
    });
903
 
904
 
905
#       
906
# Timer: 1s - TTS Sprachausgabe
907
#
908
$frame_map_top->repeat (1000, sub
909
    {
910
    # Aktuell gültige Karte
911
    %Map = %{$Maps{'Current'}};
912
 
913
    lock (%MkOsd);              # until end of block
914
 
915
    my $StatusInterval = $Cfg->{'tts'}->{'StatusInterval'}  || "30";  
916
    $SpeechTimer ++;
917
    my @TtsMsg;
918
 
919
    if ( &MkOsdIsValid() )
920
        {
921
        # Gueltige OSD Daten
922
 
923
        if ( $SpeechTimer % $StatusInterval == 0 )
924
            {
925
            #
926
            # give system status, low prio messages
927
            #
928
 
929
            # clear old low prio messages
930
            &TtsClear('LOW');
931
 
932
            # get messages from configuration
933
            for ( $Message = 1; $Message < 10; $Message ++)
934
                {
935
                my $MsgId = sprintf ("Message%d", $Message);
936
                my $Msg = $Cfg->{'tts'}{$MsgId};
937
 
938
                if ( $Msg =~ /FLIGHT_TIME/i )
939
                    {
940
                    # Flight time
941
                    my $Min = int ( ($MkFlyingTime +1) / 60);
942
                    my $Sec = int ( ($MkFlyingTime +1) % 60);
943
                    my $Text = sprintf ("$Translate{'TtsFlightTimeMinSec'}", $Min, $Sec);
944
                    if ( $Min == 0 )
945
                        {
946
                        $Text = sprintf ("$Translate{'TtsFlightTimeSec'}", $Sec);
947
                        }
948
                   &TtsSpeak ('LOW', "$Text");
949
                   }
950
 
951
                elsif ( $Msg =~ /BATTERY/i )
952
                    {
953
                    # Battery
954
                    my ($Volt, $Tenth) = split '\.', $System{'UBat'};
955
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsBattery'}", $Volt, $Tenth));
956
                    }
957
 
958
                elsif ( $Msg =~ /ALTITUDE/i )
959
                    {
960
                    # Altitude
961
                    if ( $System{'Alt'} < 0 )
962
                        {
963
                        &TtsSpeak ('LOW', sprintf ("$Translate{'TtsAltitudeNegative'}", abs($System{'Alt'}) ) );
964
                        }
965
                    else
966
                        {
967
                        &TtsSpeak ('LOW', sprintf ("$Translate{'TtsAltitude'}", $System{'Alt'} ) );
968
                        }
969
                    }
970
 
971
                elsif ( $Msg =~ /SATELLITES/i )
972
                    {
973
                    # Satellites
974
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsSatellite'}",  $MkOsd{'SatsInUse'}) );
975
                    }
976
 
977
                elsif ( $Msg =~ /HOME_DIST/i )
978
                    {
979
                    # Home Distance
980
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsHomeDist'}",  $System{'HomeDist'}) );
981
                    }
982
 
983
                elsif ( $Msg =~ /TARGET_DIST/i )
984
                    {
985
                    # Target Distance
986
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsTargetDist'}",  $System{'TargetDist'}) );
987
                    }
988
 
989
                elsif ( $Msg ne "" )
990
                    {
991
                    # text or perl code
992
                    &TtsSpeak ('LOW', eval "$Msg" );
993
                    }
994
                }
995
            }
996
 
997
       # high prio messages
998
       if ( $SpeechTimer % 5 == 0 )
999
            {
1000
            if ( $System{'BatWarning'} )          { push @TtsMsg, $Translate{'TtsBatteryWarning'}; }
1001
            if ( $System{'RCQuality'} eq "WEAK" ) { push @TtsMsg, $Translate{'TtsRcWeak'}; }
1002
            if ( $System{'RCQuality'} eq "NO" )   { push @TtsMsg, $Translate{'TtsRcNo'}; }
1003
            if ( $System{'CrossingBorder'} )      { push @TtsMsg, $Translate{'TtsCrossingBorder'}; }
1004
            if ( $System{'OutsideBorder'} )       { push @TtsMsg, $Translate{'TtsOutsideAirfield'}; }
1005
            if ( $System{'RangeWarning'} )        { push @TtsMsg, $Translate{'TtsRange'}; }
1006
            }
1007
        }
1008
    else
1009
        {
1010
        # no data link
1011
        if ( $SpeechTimer % 5 == 0 )
1012
            {
1013
            push @TtsMsg, $Translate{'TtsNoDataLink'};
1014
            }
1015
        }
1016
 
1017
    # speak high prio messages 
1018
    if ( scalar @TtsMsg > 0 )
1019
        {
1020
        # Clear pending messsages
1021
        &TtsClear('HIGH');
1022
 
1023
        # Speak collected messages
1024
        foreach $Msg (@TtsMsg)
1025
            {
1026
            &TtsSpeak ('HIGH', $Msg);
1027
            }
1028
        }
1029
    });
1030
 
1031
 
1032
 
1033
#
1034
#  Event engine
1035
#
1036
 
1037
$frame_map_top->repeat (50, sub     # 50ms
1038
    {
1039
    foreach $Key (keys %{$Event})
1040
        {
1041
        if ( ref $Event->{$Key} eq "" )
1042
            {
1043
            # ignore: CreationDate, Version
1044
            next;
1045
            }
1046
 
1047
        my $Active      = $Event->{$Key}->{'Active'};
1048
        my $Trigger     = $Event->{$Key}->{'Trigger'};       # RISE, FALL, TOGGLE_RISE, TOGGLE_FALL, TRUE, FALSE
1049
        my $Condition   = $Event->{$Key}->{'Condition'};     # Perl commands, Return 1, if Condition is true
1050
        my $Action      = $Event->{$Key}->{'Action'};        # Perl commands
1051
        my $ActionElse  = $Event->{$Key}->{'ActionElse'};    # Perl commands
1052
        my $Delay       = $Event->{$Key}->{'Delay'};         # Action rearm delay in ms
1053
        my $Repeat      = $Event->{$Key}->{'Repeat'};        # Action repeat time in  ms
1054
        my $RepeatElse  = $Event->{$Key}->{'RepeatElse'};    # ActionElse repeat time in ms
1055
 
1056
        my $DoAction = 0;
1057
 
1058
        if ( $Active =~ /y/i  and  $Condition ne "" )
1059
            {
1060
 
1061
            # Provide info about current Event
1062
            %MyEvent = %{$Event->{$Key}};
1063
            $MyEvent{'EventName'} = $Key;
1064
 
1065
            # lock all shared hashes, which might me used in eval-code
1066
            lock (%MkOsd);           # until end of block
1067
            lock (%MkSerialChannel); # until end of block
1068
            lock (%Stick);           # until end of block
1069
            lock (%MkNcDebug);       # until end of block
1070
 
1071
            # compile and execute condition at runtime. Return 1, if Condition is true
1072
            my $CondStat = eval "$Condition";    
1073
            if ( $@ ne "" )
1074
                {
1075
                # print compiler message to STDOUT
1076
                print "Event $Key: Condition: $@";
1077
                }
1078
 
1079
            # current time for Delay, Repeat timing
1080
            my ($t0_s, $t0_us) = gettimeofday;
1081
 
1082
            # for Condition edge detection
1083
            my $LastCondStat = $EventStat{$Key}{'LastCondValue'};
1084
            $EventStat{$Key}{'LastCondValue'} = $CondStat;
1085
 
1086
            # some flags
1087
            my $Rise  = (! $LastCondStat  and    $CondStat);
1088
            my $Fall  = (  $LastCondStat  and  ! $CondStat);
1089
            my $True  =   $CondStat;
1090
            my $False = ! $CondStat;
1091
 
1092
            # Delay time check
1093
            my $DelayCheck = 1;
1094
            my $t1_s  =  $EventStat{$Key}{'DelayTime_s'};
1095
            my $t1_us =  $EventStat{$Key}{'DelayTime_us'};
1096
            if ( $Delay ne "" and  $t1_s ne ""  and  $t1_us ne "" )
1097
                {
1098
                my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
1099
                if ( $Diff_ms < $Delay )
1100
                    {
1101
                    $DelayCheck = 0;
1102
                    }
1103
                }
1104
 
1105
            #
1106
            # rising or falling edge
1107
            #
1108
            if ( ($Trigger eq "RISE" and  $Rise)  or
1109
                 ($Trigger eq "FALL" and  $Fall) )
1110
                {
1111
                $DoAction = $DelayCheck;
1112
 
1113
                if ( $DoAction )
1114
                    {
1115
                    $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
1116
                    $EventStat{$Key}{'DelayTime_us'} = $t0_us;
1117
 
1118
                    # reset Repeat time when event gets active
1119
                    $EventStat{$Key}{'RepeatTime_s'}  = 0;
1120
                    $EventStat{$Key}{'RepeatTime_us'} = 0;
1121
 
1122
                    # reset event counter
1123
                    $EventStat{$Key}{'EventCnt'} = 0;
1124
                    }
1125
                }
1126
 
1127
            #
1128
            # toggle on rising/falling edge
1129
            #
1130
            elsif ( $Trigger =~ /TOGGLE/i )
1131
                {
1132
                $DoAction = 0;
1133
                if ( ($Rise  and  $Trigger eq "TOGGLE_RISE")  or
1134
                     ($Fall  and  $Trigger eq "TOGGLE_FALL") )
1135
                    {
1136
                    $DoAction = $DelayCheck;
1137
 
1138
                    if ( $DoAction )
1139
                        {
1140
                        $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
1141
                        $EventStat{$Key}{'DelayTime_us'} = $t0_us;
1142
 
1143
                        # reset Repeat time when event gets active
1144
                        $EventStat{$Key}{'RepeatTime_s'}  = 0;
1145
                        $EventStat{$Key}{'RepeatTime_us'} = 0;
1146
 
1147
                        # reset event counter
1148
                        $EventStat{$Key}{'EventCnt'} = 0;
1149
                        }
1150
                    }
1151
 
1152
                if ( $DoAction )
1153
                    {
1154
                    $EventStat{$Key}{'ToggleOnOff'} ^= 1;
1155
                    }
1156
 
1157
                $DoAction = $EventStat{$Key}{'ToggleOnOff'};
1158
                }
1159
 
1160
 
1161
            #
1162
            # TRUE Condition
1163
            #
1164
            elsif ( $Trigger eq "TRUE" )
1165
                {
1166
                if ( $True )
1167
                    {
1168
                    $DoAction = $DelayCheck;
1169
 
1170
                    if ( $DoAction and $Rise)
1171
                        {
1172
                        # reset Repeat time when event gets active
1173
                        $EventStat{$Key}{'RepeatTime_s'}  = 0;
1174
                        $EventStat{$Key}{'RepeatTime_us'} = 0;
1175
 
1176
                        # reset event counter
1177
                        $EventStat{$Key}{'EventCnt'} = 0;
1178
                        }
1179
                    }
1180
 
1181
                if ( $Fall )
1182
                    {
1183
                    # set Delay reference from falling edge
1184
 
1185
                    if ( $DelayCheck )
1186
                        {
1187
                        # timestamp of last falling Condition edge change
1188
                        $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
1189
                        $EventStat{$Key}{'DelayTime_us'} = $t0_us;
1190
 
1191
                        # reset event counter
1192
                        $EventStat{$Key}{'EventCnt'} = 0;
1193
                        }
1194
                    }
1195
                }
1196
 
1197
            #
1198
            # FALSE Condition
1199
            #
1200
            elsif ( $Trigger eq "FALSE" )
1201
                {
1202
                if ( $False )
1203
                    {
1204
                    $DoAction = $DelayCheck;
1205
 
1206
                    if ( $DoAction and $Fall)
1207
                        {
1208
                        # reset Repeat time when event gets active
1209
                        $EventStat{$Key}{'RepeatTime_s'}  = 0;
1210
                        $EventStat{$Key}{'RepeatTime_us'} = 0;
1211
 
1212
                        # reset event counter
1213
                        $EventStat{$Key}{'EventCnt'} = 0;
1214
                        }
1215
                    }
1216
 
1217
                if ( $Rise )
1218
                    {
1219
                    # set Delay reference from rising edge
1220
 
1221
                    if ( $DelayCheck )
1222
                        {
1223
                        # timestamp of last rising Condition edge change
1224
                        $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
1225
                        $EventStat{$Key}{'DelayTime_us'} = $t0_us;
1226
 
1227
                        # reset event counter
1228
                        $EventStat{$Key}{'EventCnt'} = 0;
1229
                        }
1230
                    }
1231
                }
1232
 
1233
            else
1234
                {
1235
                # unkown, fall through
1236
                }
1237
 
1238
 
1239
            # undefined Status, neither ON, nor OFF
1240
            $EventStat{$Key}{'Status'} = "";
1241
 
1242
            #
1243
            # Process Action
1244
            #
1245
            if ( $DoAction  and  $Action ne "" )
1246
                {
1247
                $EventStat{$Key}{'Status'} = "ON";
1248
                my $Execute = 1;
1249
 
1250
                my $t1_s  =  $EventStat{$Key}{'RepeatTime_s'};
1251
                my $t1_us =  $EventStat{$Key}{'RepeatTime_us'};
1252
                if ( $Repeat ne "" and  $t1_s ne ""  and  $t1_us ne "" )
1253
                    {
1254
                    my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
1255
                    if ( $Diff_ms < $Repeat )
1256
                        {
1257
                        $Execute = 0;
1258
                        }
1259
                    }
1260
 
1261
                if ( $Execute )
1262
                    {
1263
                    # Accounting
1264
                    $MyEvent{'EventCnt'} = $EventStat{$Key}{'EventCnt'} ++;
1265
 
1266
                    # compile and execute action at runtime
1267
                    eval "$Action";
1268
                    if ( $@ ne "" )
1269
                        {
1270
                        # print compiler message to STDOUT
1271
                        print "Event $Key: Action: $@";
1272
                        }
1273
 
1274
                    # Timestamp, when Action was executed
1275
                    $EventStat{$Key}{'RepeatTime_s'}  = $t0_s;
1276
                    $EventStat{$Key}{'RepeatTime_us'} = $t0_us;
1277
 
1278
                    # reset ActionElse repeat timing
1279
                    $EventStat{$Key}{'RepeatTimeElse_s'}  = 0;
1280
                    $EventStat{$Key}{'RepeatTimeElse_us'} = 0;
1281
                    }
1282
                }
1283
 
1284
            #
1285
            # Process ActionElse
1286
            #
1287
            if (  ! $DoAction  and  $ActionElse ne "" )
1288
                {
1289
                $EventStat{$Key}{'Status'} = "OFF";
1290
                my $Execute = 1;
1291
 
1292
                # check repeat time
1293
                my $t1_s  =  $EventStat{$Key}{'RepeatTimeElse_s'};
1294
                my $t1_us =  $EventStat{$Key}{'RepeatTimeElse_us'};
1295
 
1296
                if ( $RepeatElse ne "" and  $t1_s ne ""  and  $t1_us ne "" )
1297
                    {
1298
                    my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
1299
                    if ( $Diff_ms < $RepeatElse )
1300
                        {
1301
                        $Execute = 0;
1302
                        }
1303
                    }
1304
 
1305
                if ( $Execute )
1306
                    {
1307
                    # Accounting
1308
                    $MyEvent{'EventCnt'} = $EventStat{$Key}{'EventCnt'} ++;
1309
 
1310
                    # compile and execute action at runtime
1311
                    eval "$ActionElse";
1312
                    if ( $@ ne "" )
1313
                        {
1314
                        # print compiler message to STDOUT
1315
                        print "Event $Key: ActionElse: $@";
1316
                        }
1317
 
1318
                    # Timestamp, when ActionElse was executed
1319
                    $EventStat{$Key}{'RepeatTimeElse_s'}  = $t0_s;
1320
                    $EventStat{$Key}{'RepeatTimeElse_us'} = $t0_us;
1321
                    }
1322
                }
1323
            }
1324
 
1325
        else
1326
            {
1327
            $EventStat{$Key}{'Status'} = "DISABLED";
1328
            }
1329
        }
1330
    });
1331
 
1332
 
1333
# parse input controls for one output channel
1334
# Syntax: Control1_Reverse,min,max,expo,limit + Control2,min,max,expo,limit + ...
1335
sub ParseControls
1336
    {
1337
    my ($Channel, $ControlVal, $Expo, $Limit) = @_;
1338
 
1339
    # Channel output can be sum of multiple input controls
1340
    $ChannelVal = 0;
1341
    $ControlVal =~ s/ //g;
1342
    @Controls = split '\+', $ControlVal;
1343
    foreach $ControlVal (@Controls)
1344
        {
1345
        my ($Control, $Min, $Max, $ControlExpo, $ControlLimit) = split ',', $ControlVal;
1346
        if ( $Min eq "" ) { $Min = -125; }
1347
        if ( $Max eq "" ) { $Max =  125; }
1348
 
1349
        my ($Control, $Reverse) = split '_', $Control;
1350
        my $Val = 0;
1351
 
1352
        if ( $Control ne "" )
1353
            {
1354
            # Joystick Button
1355
            if ( $Control =~ /^JoystickButton(\d+)/i )
1356
                {
1357
                my $Button = $1 - 1;
1358
                $ChannelVal = $Min;
1359
                if ( &JoystickButton($Button) )
1360
                    {
1361
                    $Val = $Max;
1362
                    }
1363
                }
1364
 
1365
            # Joystick POV Button
1366
            elsif ( $Control =~ /^JoystickPov(\d+)/i )
1367
                {
1368
                my $Angle = $1;
1369
                my $Pov = $Stick{'JoystickPov'} / 100;
1370
                $Val = $Min;
1371
                if ( $Pov == $Angle )
1372
                    {
1373
                    $Val = $Max;
1374
                    }
1375
                }
1376
 
1377
            # Mouse Button
1378
            elsif ( $Control =~ /^MouseButton(\d+)/i )
1379
                {
1380
                my $Button = $1 - 1;
1381
                $Val = $Min;
1382
                if ( &MouseButton($Button) )
1383
                    {
1384
                    $Val = $Max;
1385
                    }
1386
                }
1387
 
1388
            # Serial Channel
1389
            elsif ( $Control =~ /^SerialChannel/i )
1390
                {
1391
                $Val = $MkSerialChannel{$Control};
1392
                }
1393
 
1394
            # fixed value
1395
            elsif ( $Control =~ /^(-*\d+)/i )
1396
                {
1397
                $Val = $1;
1398
                }
1399
 
1400
            # analog stick
1401
            else
1402
                {
1403
                # Scale Stick 0..StickRange to -125..0..125
1404
                $Val = $Stick{$Control} / $Stick{'StickRange'} * 250 - 125;
1405
                if ( $Reverse =~ /Reverse/i )
1406
                    {
1407
                    $Val = - $Val;
1408
                    }
1409
                }
1410
 
1411
 
1412
            # Expo/Limit for each input control
1413
            if ( "ExternControlGas ExternControlHeight" =~ /$Channel/ )
1414
                {
1415
                $Val += 125;     # -125..0..125 -> 0..250
1416
                $Val = &ExpoLimit (0, 250, $Val, $ControlExpo, $ControlLimit);
1417
                }
1418
            else
1419
                {
1420
                # all other symmetrical channels -125..0..125
1421
                $Val = &ExpoLimit (-125, 125, $Val, $ControlExpo, $ControlLimit);
1422
                }
1423
 
1424
            $ChannelVal += $Val;
1425
            }
1426
        }
1427
 
1428
    # Expo/Limit for output channel
1429
    if ( "ExternControlGas ExternControlHeight" =~ /$Channel/ )
1430
        {
1431
        # asymmetrical channels 0..250
1432
        $ChannelVal = &ExpoLimit (0, 255, $ChannelVal, $Expo, $Limit);
1433
        $ChannelVal = int ($ChannelVal + 0.5);
1434
        $ChannelVal = &CheckUnsignedChar($ChannelVal);
1435
        }
1436
    else
1437
        {
1438
        # all other symmetrical channels -125..0..125
1439
        $ChannelVal = &ExpoLimit (-125, 125, $ChannelVal, $Expo, $Limit);
1440
        $ChannelVal = int ($ChannelVal + 0.5);
1441
        $ChannelVal = &CheckSignedChar($ChannelVal);
1442
        }
1443
 
1444
    return $ChannelVal;
1445
    }
1446
 
1447
 
1448
#
1449
# Send Serial Channel + stick button/analog handling
1450
#
1451
 
1452
my $SerialChannelTiming = $Cfg->{'serialchannel'}->{'SerialChannelFrquency'} || 20;
1453
$SerialChannelTiming = int (1 / $SerialChannelTiming * 1000);
1454
if ( $SerialChannelTiming < 10 )
1455
    {
1456
    $SerialChannelTiming = 10;
1457
    }
1458
 
1459
# Timer: variable timing
1460
$frame_map_top->repeat ($SerialChannelTiming, sub
1461
    {
1462
    # 12 serial Channel
1463
    for ( my $ChannelInd = 0; $ChannelInd < 12; $ChannelInd ++)
1464
        {
1465
        my $Channel = sprintf ("SerialChannel%02d", $ChannelInd + 1);  # key for Cfg-hash
1466
 
1467
        my $Control = $Cfg->{'serialchannel'}->{$Channel};
1468
        if ( $Control ne "" )
1469
            {
1470
            my $Expo    = $Cfg->{'serialchannel'}->{$Channel . "Expo"};
1471
            my $Limit   = $Cfg->{'serialchannel'}->{$Channel . "Limit"};
1472
            my $ChannelVal = &ParseControls($Channel, $Control, $Expo, $Limit);
1473
 
1474
            &SerialChannel($ChannelInd, $ChannelVal);
1475
            }
1476
        }
1477
 
1478
    $MkSerialChannel{'SerialChannelSend'}   = $Cfg->{'serialchannel'}->{'SerialChannelSend'};
1479
    $MkSerialChannel{'SerialChannelTiming'} = $SerialChannelTiming;
1480
 
1481
    # send serial Channel to MK, nicht wenn deaktiviert  oder WP uebertragen werden
1482
    if ( ! $MkSendWp  and  $Cfg->{'serialchannel'}->{'SerialChannelSend'} =~ /y/i  and  $TxExtOn == 1)
1483
        {
1484
        &SendSerialChannel();  # send values in %MkSerialChannel
1485
        }
1486
    });
1487
 
1488
 
1489
#
1490
# Extern Control
1491
#
1492
 
1493
my $ExternControlTiming = $Cfg->{'externcontrol'}->{'ExternControlFrquency'} || 20;
1494
$ExternControlTiming = int (1 / $ExternControlTiming * 1000);
1495
if ( $ExternControlTiming < 10 )
1496
    {
1497
    $ExternControlTiming = 10;
1498
    }
1499
 
1500
# Timer: variable timing
1501
$frame_map_top->repeat ($ExternControlTiming, sub
1502
    {
1503
    foreach my $Channel ( qw(ExternControlRoll ExternControlNick ExternControlGier ExternControlGas ExternControlHeight) )
1504
        {
1505
        my $Control = $Cfg->{'externcontrol'}->{$Channel};
1506
        if ( $Control ne "" )
1507
            {
1508
            my $Expo    = $Cfg->{'externcontrol'}->{$Channel . "Expo"};
1509
            my $Limit   = $Cfg->{'externcontrol'}->{$Channel . "Limit"};
1510
            $MkExternControl{$Channel} = &ParseControls($Channel, $Control, $Expo, $Limit);
1511
            }
1512
        }
1513
 
1514
    $MkExternControl{'ExternControlSend'}   = $Cfg->{'externcontrol'}->{'ExternControlSend'};
1515
    $MkExternControl{'ExternControlTimimg'} = $ExternControlTiming;
1516
 
1517
    # send extern control to MK, nicht wenn deaktiviert oder WP uebertragen werden
1518
    if ( ! $MkSendWp  and  $Cfg->{'externcontrol'}->{'ExternControlSend'} =~ /y/i  and  $TxExtOn == 1 )
1519
        {
1520
        &SendExternalControl ( '-nick'   => $MkExternControl{'ExternControlNick'},
1521
                               '-roll'   => $MkExternControl{'ExternControlRoll'},
1522
                               '-gier'   => $MkExternControl{'ExternControlGier'},
1523
                               '-gas'    => $MkExternControl{'ExternControlGas'},
1524
                               '-height' => $MkExternControl{'ExternControlHeight'},
1525
                               '-remotebuttons' => 0,
1526
                               '-config' => 1,
1527
                               '-frame'  => 1,
1528
                             );
1529
 
1530
        $MkExternControl{'_Timestamp'} = time;   # time when command was sent
1531
        }
1532
    });
1533
 
1534
 
1535
#
1536
# mouse/joystick crosshair move in player pause mode
1537
#
1538
 
1539
$frame_map_top->repeat (50, sub     # 50ms
1540
    {
1541
    if ( $PlayerMode eq 'Pause'  and
1542
         $PlayerPause_Lat ne ""  and  $PlayerPause_Lon ne "" )
1543
        {
1544
        if ( $Stick{'_JoystickTimestamp'} >= time-1  or  $Stick{'_MouseTimestamp'} >= time-2 )
1545
            {
1546
            lock (%MkOsd);           # until end of block
1547
 
1548
            my $ControlX = $Cfg->{'map'}->{'CrosshairMoveX'};
1549
            my $ControlY = $Cfg->{'map'}->{'CrosshairMoveY'};
1550
 
1551
            if ( $ControlX ne ""  and $ControlY ne "" )
1552
                {
1553
                my $StickRange = 250;
1554
                my $Bias = $StickRange /2;
1555
 
1556
                my $ExpoX    = $Cfg->{'map'}->{'CrosshairMoveXExpo'};
1557
                my $LimitX   = $Cfg->{'map'}->{'CrosshairMoveXLimit'};
1558
                my $SpeedX  = &ParseControls('CrosshairMoveX', $ControlX, $ExpoX, $LimitX);
1559
 
1560
                my $ExpoY    = $Cfg->{'map'}->{'CrosshairMoveYExpo'};
1561
                my $LimitY   = $Cfg->{'map'}->{'CrosshairMoveYLimit'};
1562
                my $SpeedY  = &ParseControls('CrosshairMoveY', $ControlY, $ExpoY, $LimitY);
1563
 
1564
                my $BearingTop = &MapAngel() - 90.0;
1565
                my $BearingMouse = rad2deg atan2($SpeedX, $SpeedY);
1566
                my $Bearing = $BearingTop + $BearingMouse;
1567
 
1568
                if ( $PlayerPauseMode eq "MK" )
1569
                    {
1570
                    # MK Reference
1571
                    $Bearing = $MkOsd{'CompassHeading'} + $BearingMouse;
1572
                    }
1573
 
1574
                # max. 30m/s at 50ms frame time
1575
                my $Speed = sqrt ($SpeedX * $SpeedX + $SpeedY * $SpeedY);
1576
                my $Dist = 40*50/1000 * $Speed / $Bias;
1577
 
1578
                ($PlayerPause_Lat, $PlayerPause_Lon) = &MapGpsAt($PlayerPause_Lat, $PlayerPause_Lon, $Dist, $Bearing);
1579
 
1580
                if ( $SpeedX != 0  or  $SpeedY != 0 )
1581
                    {
1582
                    $CrosshairTimerCnt = 0;
1583
                    }
1584
                }
1585
            }
1586
 
1587
        #
1588
        # show/update Crosshair for 5 sec after move
1589
        #
1590
 
1591
        if ( $CrosshairTimerCnt < 5 * 1000/50)
1592
           {
1593
           &CrosshairShow ($PlayerPause_Lat, $PlayerPause_Lon);
1594
           }
1595
        elsif ( $CrosshairTimerCnt ==  5 * 1000/50)
1596
           {
1597
           &CrosshairHide();
1598
           }
1599
 
1600
        $CrosshairTimerCnt ++;
1601
        }
1602
 
1603
    });
1604
 
1605
 
1606
__END__