]> git.mxchange.org Git - flightgear.git/blob - src/Network/ATC-Outputs.cxx
773d57d8265b4e4d8fffed46902a974cc6ae3691
[flightgear.git] / src / Network / ATC-Outputs.cxx
1 // ATC-Outputs.hxx -- Translate FGFS properties to ATC hardware outputs.
2 //
3 // Written by Curtis Olson, started November 2004.
4 //
5 // Copyright (C) 2004  Curtis L. Olson - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 //
21 // $Id$
22
23
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27
28 #include <simgear/compiler.h>
29
30 #if defined( unix ) || defined( __CYGWIN__ )
31 #  include <sys/types.h>
32 #  include <sys/stat.h>
33 #  include <fcntl.h>
34 #endif
35
36 #include STL_STRING
37
38 #include <simgear/debug/logstream.hxx>
39
40 #include <Main/fg_props.hxx>
41
42 #include "ATC-Outputs.hxx"
43
44 SG_USING_STD(string);
45
46
47
48 // Lock the ATC hardware
49 static int ATCLock( int fd ) {
50 #if defined( unix ) || defined( __CYGWIN__ )
51     // rewind
52     lseek( fd, 0, SEEK_SET );
53
54     char tmp[2];
55     int result = read( fd, tmp, 1 );
56     if ( result != 1 ) {
57         SG_LOG( SG_IO, SG_DEBUG, "Lock failed" );
58     }
59
60     return result;
61 #else
62     return -1;
63 #endif
64 }
65
66
67 // Release the ATC hardware
68 static int ATCRelease( int fd ) {
69 #if defined( unix ) || defined( __CYGWIN__ )
70     // rewind
71     lseek( fd, 0, SEEK_SET );
72
73     char tmp[2];
74     tmp[0] = tmp[1] = 0;
75     int result = write( fd, tmp, 1 );
76
77     if ( result != 1 ) {
78         SG_LOG( SG_IO, SG_DEBUG, "Release failed" );
79     }
80
81     return result;
82 #else
83     return -1;
84 #endif
85 }
86
87
88 // Constructor: The _board parameter specifies which board to
89 // reference.  Possible values are 0 or 1.  The _config_file parameter
90 // specifies the location of the output config file (xml)
91 FGATCOutput::FGATCOutput( const int _board, const SGPath &_config_file ) :
92     is_open(false),
93     lamps_out_node(NULL),
94     radio_display_node(NULL),
95     steppers_node(NULL)
96 {
97     board = _board;
98     config = _config_file;
99 }
100
101
102 // Write a radios command
103 static int ATCSetRadios( int fd, unsigned char data[ATC_RADIO_DISPLAY_BYTES] ) {
104 #if defined( unix ) || defined( __CYGWIN__ )
105     // rewind
106     lseek( fd, 0, SEEK_SET );
107
108     int result = write( fd, data, ATC_RADIO_DISPLAY_BYTES );
109
110     if ( result != ATC_RADIO_DISPLAY_BYTES ) {
111         SG_LOG( SG_IO, SG_DEBUG, "Write failed" );
112     }
113
114     return result;
115 #else
116     return -1;
117 #endif
118 }
119
120
121 // Write a stepper command
122 static int ATCSetStepper( int fd, unsigned char channel,
123                               unsigned char value )
124 {
125 #if defined( unix ) || defined( __CYGWIN__ )
126     // rewind
127     lseek( fd, 0, SEEK_SET );
128
129     // Write the value
130     unsigned char buf[3];
131     buf[0] = channel;
132     buf[1] = value;
133     buf[2] = 0;
134     int result = write( fd, buf, 2 );
135     if ( result != 2 ) {
136         SG_LOG( SG_IO, SG_INFO, "Write failed" );
137     }
138     SG_LOG( SG_IO, SG_DEBUG,
139             "Sent cmd = " << (int)channel << " value = " << (int)value );
140     return result;
141 #else
142     return -1;
143 #endif
144 }
145
146
147 // Read status of last stepper written to
148 static unsigned char ATCReadStepper( int fd ) {
149 #if defined( unix ) || defined( __CYGWIN__ )
150     int result;
151
152     // rewind
153     lseek( fd, 0, SEEK_SET );
154
155     // Write the value
156     unsigned char buf[2];
157     result = read( fd, buf, 1 );
158     if ( result != 1 ) {
159         SG_LOG( SG_IO, SG_ALERT, "Read failed" );
160         exit( -1 );
161     }
162     SG_LOG( SG_IO, SG_DEBUG, "Read result = " << (int)buf[0] );
163
164     return buf[0];
165 #else
166     return 0;
167 #endif
168 }
169
170
171 // Turn a lamp on or off
172 void ATCSetLamp( int fd, int channel, bool value ) {
173 #if defined( unix ) || defined( __CYGWIN__ )
174     // lamp channels 0-63 are written to LampPort0, channels 64-127
175     // are written to LampPort1
176
177     // bits 0-6 are the lamp address
178     // bit 7 is the value (on/off)
179
180     int result;
181
182     // Write the value
183     unsigned char buf[3];
184     buf[0] = channel;
185     buf[1] = value;
186     buf[2] = 0;
187     result = write( fd, buf, 2 );
188     if ( result != 2 ) {
189         SG_LOG( SG_IO, SG_ALERT,  "Write failed" );
190         exit( -1 );
191     }
192 #endif
193 }
194
195
196 void FGATCOutput::init_config() {
197 #if defined( unix ) || defined( __CYGWIN__ )
198     if ( config.str()[0] != '/' ) {
199         // not an absolute path, prepend the standard location
200         SGPath tmp;
201         char *envp = ::getenv( "HOME" );
202         if ( envp != NULL ) {
203             tmp = envp;
204             tmp.append( ".atcflightsim" );
205             tmp.append( config.str() );
206             config = tmp;
207         }
208     }
209     readProperties( config.str(), globals->get_props() );
210 #endif
211 }
212
213
214 // Open and initialize the ATC hardware
215 bool FGATCOutput::open( int lock_fd ) {
216     if ( is_open ) {
217         SG_LOG( SG_IO, SG_ALERT, "This board is already open for output! "
218                 << board );
219         return false;
220     }
221
222     // This loads the config parameters generated by "simcal"
223     init_config();
224
225     SG_LOG( SG_IO, SG_ALERT,
226             "Initializing ATC output hardware, please wait ..." );
227
228     snprintf( lamps_file, 256,
229               "/proc/atcflightsim/board%d/lamps", board );
230     snprintf( radio_display_file, 256,
231               "/proc/atcflightsim/board%d/radios", board );
232     snprintf( stepper_file, 256,
233               "/proc/atcflightsim/board%d/steppers", board );
234
235 #if defined( unix ) || defined( __CYGWIN__ )
236
237     /////////////////////////////////////////////////////////////////////
238     // Open the /proc files
239     /////////////////////////////////////////////////////////////////////
240
241     lamps_fd = ::open( lamps_file, O_WRONLY );
242     if ( lamps_fd == -1 ) {
243         SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
244         char msg[256];
245         snprintf( msg, 256, "Error opening %s", lamps_file );
246         perror( msg );
247         exit( -1 );
248     }
249
250     radio_display_fd = ::open( radio_display_file, O_RDWR );
251     if ( radio_display_fd == -1 ) {
252         SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
253         char msg[256];
254         snprintf( msg, 256, "Error opening %s", radio_display_file );
255         perror( msg );
256         exit( -1 );
257     }
258
259     stepper_fd = ::open( stepper_file, O_RDWR );
260     if ( stepper_fd == -1 ) {
261         SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
262         char msg[256];
263         snprintf( msg, 256, "Error opening %s", stepper_file );
264         perror( msg );
265         exit( -1 );
266     }
267
268 #endif
269
270 #ifdef ATCFLIGHTSIM_HAVE_COMPASS
271     /////////////////////////////////////////////////////////////////////
272     // Home the compass stepper motor
273     /////////////////////////////////////////////////////////////////////
274
275     SG_LOG( SG_IO, SG_ALERT,
276             "  - Homing the compass stepper motor" );
277
278     // Lock the hardware, keep trying until we succeed
279     while ( ATCLock( lock_fd ) <= 0 );
280
281     // Send the stepper home command
282     ATCSetStepper( stepper_fd, ATC_COMPASS_CH, ATC_STEPPER_HOME );
283
284     // Release the hardware
285     ATCRelease( lock_fd );
286
287     SG_LOG( SG_IO, SG_ALERT,
288             "  - Waiting for compass to come home." );
289
290     bool home = false;
291     int timeout = 900;          // about 30 seconds
292     timeout = 0;
293     while ( ! home && timeout > 0 ) {
294         if ( timeout % 150 == 0 ) {
295             SG_LOG( SG_IO, SG_INFO, "waiting for compass = " << timeout );
296         } else {
297             SG_LOG( SG_IO, SG_DEBUG, "Checking if compass home ..." );
298         }
299
300         while ( ATCLock( lock_fd ) <= 0 );
301
302         unsigned char result = ATCReadStepper( stepper_fd );
303         if ( result == 0 ) {
304             home = true;
305         }
306
307         ATCRelease( lock_fd );
308
309 #if defined( _MSC_VER )
310         ulMilliSecondSleep(33);
311 #elif defined (WIN32) && !defined(__CYGWIN__)
312         Sleep (33);
313 #else
314         usleep(33);
315 #endif
316
317         --timeout;
318     }
319
320     compass_position = 0.0;
321 #endif
322
323     /////////////////////////////////////////////////////////////////////
324     // Blank the radio display
325     /////////////////////////////////////////////////////////////////////
326
327     SG_LOG( SG_IO, SG_ALERT,
328             "  - Clearing the radios displays." );
329
330     // Prepair the data
331     unsigned char value = 0xff;
332     for ( int channel = 0; channel < ATC_RADIO_DISPLAY_BYTES; ++channel ) {
333         radio_display_data[channel] = value;
334     }
335
336     // Lock the hardware, keep trying until we succeed
337     while ( ATCLock( lock_fd ) <= 0 );
338
339     // Set radio display
340     ATCSetRadios( radio_display_fd, radio_display_data );
341
342     ATCRelease( lock_fd );
343
344     /////////////////////////////////////////////////////////////////////
345     // Blank the lamps
346     /////////////////////////////////////////////////////////////////////
347
348     for ( int i = 0; i < 128; ++i ) {
349         ATCSetLamp( lamps_fd, i, false );
350     }
351
352     /////////////////////////////////////////////////////////////////////
353     // Finished initing hardware
354     /////////////////////////////////////////////////////////////////////
355
356     SG_LOG( SG_IO, SG_ALERT,
357             "Done initializing ATC output hardware." );
358
359     is_open = true;
360
361     /////////////////////////////////////////////////////////////////////
362     // Connect up to property values
363     /////////////////////////////////////////////////////////////////////
364
365     char base_name[256];
366
367     snprintf( base_name, 256, "/output/atc-board[%d]/lamps", board );
368     lamps_out_node = fgGetNode( base_name );
369
370     snprintf( base_name, 256, "/output/atc-board[%d]/radio-display", board );
371     radio_display_node = fgGetNode( base_name );
372
373     snprintf( base_name, 256, "/output/atc-board[%d]/steppers", board );
374     steppers_node = fgGetNode( base_name );
375
376     return true;
377 }
378
379
380 /////////////////////////////////////////////////////////////////////
381 // Write the lights
382 /////////////////////////////////////////////////////////////////////
383
384 bool FGATCOutput::do_lamps() {
385
386     if ( lamps_out_node != NULL ) {
387         for ( int i = 0; i < lamps_out_node->nChildren(); ++i ) {
388             // read the next config entry from the property tree
389
390             SGPropertyNode *child = lamps_out_node->getChild(i);
391             string cname = child->getName();
392             int index = child->getIndex();
393             string name = "";
394             string type = "";
395             SGPropertyNode *src_prop = NULL;
396             if ( cname == "lamp" ) {
397                 SGPropertyNode *prop;
398                 prop = child->getChild( "name" );
399                 if ( prop != NULL ) {
400                     name = prop->getStringValue();
401                 }
402                 prop = child->getChild( "type" );
403                 if ( prop != NULL ) {
404                     type = prop->getStringValue();
405                 }
406                 prop = child->getChild( "prop" );
407                 if ( prop != NULL ) {
408                     src_prop = fgGetNode( prop->getStringValue(), true );
409                 }
410                 ATCSetLamp( lamps_fd, index, src_prop->getBoolValue() );
411             } else {
412                 SG_LOG( SG_IO, SG_DEBUG,
413                         "Input config error, expecting 'lamp' but found "
414                         << cname );
415             }
416         }
417     }
418
419     return true;
420 }
421
422
423 /////////////////////////////////////////////////////////////////////
424 // Update the radio display 
425 /////////////////////////////////////////////////////////////////////
426
427
428 static bool navcom1_has_power() {
429     static SGPropertyNode *navcom1_bus_power
430         = fgGetNode( "/systems/electrical/outputs/nav[0]", true );
431     static SGPropertyNode *navcom1_power_btn
432         = fgGetNode( "/instrumentation/nav[0]/power-btn", true );
433
434     return (navcom1_bus_power->getDoubleValue() > 1.0)
435         && navcom1_power_btn->getBoolValue();
436 }
437
438 static bool navcom2_has_power() {
439     static SGPropertyNode *navcom2_bus_power
440         = fgGetNode( "/systems/electrical/outputs/nav[1]", true );
441     static SGPropertyNode *navcom2_power_btn
442         = fgGetNode( "/instrumentation/nav[1]/power-btn", true );
443
444     return (navcom2_bus_power->getDoubleValue() > 1.0)
445         && navcom2_power_btn->getBoolValue();
446 }
447
448 static bool dme_has_power() {
449     static SGPropertyNode *dme_bus_power
450         = fgGetNode( "/systems/electrical/outputs/dme", true );
451     
452     return (dme_bus_power->getDoubleValue() > 1.0);
453 }
454
455 static bool adf_has_power() {
456     static SGPropertyNode *adf_bus_power
457         = fgGetNode( "/systems/electrical/outputs/adf", true );
458     static SGPropertyNode *adf_power_btn
459         = fgGetNode( "/instrumentation/kr-87/inputs/power-btn", true );
460
461     return (adf_bus_power->getDoubleValue() > 1.0)
462         && adf_power_btn->getBoolValue();
463 }
464
465 static bool xpdr_has_power() {
466     static SGPropertyNode *xpdr_bus_power
467         = fgGetNode( "/systems/electrical/outputs/transponder", true );
468     static SGPropertyNode *xpdr_func_knob
469         = fgGetNode( "/instrumentation/transponder/inputs/func-knob", true );
470
471     return (xpdr_bus_power->getDoubleValue() > 1.0)
472         && (xpdr_func_knob->getIntValue() > 0);
473 }
474
475 bool FGATCOutput::do_radio_display() {
476     static SGPropertyNode *dme_serviceable
477         = fgGetNode( "/instrumentation/dme/serviceable", true );
478     static SGPropertyNode *dme_in_range
479         = fgGetNode( "/instrumentation/dme/in-range", true );
480     static SGPropertyNode *dme_min
481         = fgGetNode( "/instrumentation/dme/indicated-time-min", true );
482     static SGPropertyNode *dme_kt
483         = fgGetNode( "/instrumentation/dme/indicated-ground-speed-kt", true );
484     static SGPropertyNode *dme_nm
485         = fgGetNode( "/instrumentation/dme/indicated-distance-nm", true );
486
487     static SGPropertyNode *comm1_serviceable
488         = fgGetNode( "/instrumentation/comm[0]/serviceable", true );
489     static SGPropertyNode *com1_freq
490         = fgGetNode( "/instrumentation/comm[0]/frequencies/selected-mhz", true);
491     static SGPropertyNode *com1_stby_freq
492         = fgGetNode( "/instrumentation/comm[0]/frequencies/standby-mhz", true );
493
494     static SGPropertyNode *comm2_serviceable
495         = fgGetNode( "/instrumentation/comm[1]/serviceable", true );
496     static SGPropertyNode *com2_freq
497         = fgGetNode( "/instrumentation/comm[1]/frequencies/selected-mhz", true);
498     static SGPropertyNode *com2_stby_freq
499         = fgGetNode( "/instrumentation/comm[1]/frequencies/standby-mhz", true );
500
501     static SGPropertyNode *nav1_serviceable
502         = fgGetNode( "/instrumentation/nav[0]/serviceable", true );
503     static SGPropertyNode *nav1_freq
504         = fgGetNode( "/instrumentation/nav[0]/frequencies/selected-mhz", true );
505     static SGPropertyNode *nav1_stby_freq
506         = fgGetNode( "/instrumentation/nav[0]/frequencies/standby-mhz", true );
507
508     static SGPropertyNode *nav2_serviceable
509         = fgGetNode( "/instrumentation/nav[1]/serviceable", true );
510     static SGPropertyNode *nav2_freq
511         = fgGetNode( "/instrumentation/nav[1]/frequencies/selected-mhz", true );
512     static SGPropertyNode *nav2_stby_freq
513         = fgGetNode( "/instrumentation/nav[1]/frequencies/standby-mhz", true );
514
515     static SGPropertyNode *adf_serviceable
516         = fgGetNode( "/instrumentation/adf/serviceable", true );
517     static SGPropertyNode *adf_freq
518         = fgGetNode( "/instrumentation/kr-87/outputs/selected-khz", true );
519     static SGPropertyNode *adf_stby_freq
520         = fgGetNode( "/instrumentation/kr-87/outputs/standby-khz", true );
521     static SGPropertyNode *adf_stby_mode
522         = fgGetNode( "/instrumentation/kr-87/modes/stby", true );
523     static SGPropertyNode *adf_timer_mode
524         = fgGetNode( "/instrumentation/kr-87/modes/timer", true );
525     // static SGPropertyNode *adf_count_mode
526     //     = fgGetNode( "/instrumentation/kr-87/modes/count", true );
527     static SGPropertyNode *adf_flight_timer
528         = fgGetNode( "/instrumentation/kr-87/outputs/flight-timer", true );
529     static SGPropertyNode *adf_elapsed_timer
530         = fgGetNode( "/instrumentation/kr-87/outputs/elapsed-timer", true );
531
532     static SGPropertyNode *xpdr_serviceable
533         = fgGetNode( "/instrumentation/transponder/inputs/serviceable", true );
534     static SGPropertyNode *xpdr_func_knob
535         = fgGetNode( "/instrumentation/transponder/inputs/func-knob", true );
536     static SGPropertyNode *xpdr_flight_level
537         = fgGetNode( "/instrumentation/transponder/outputs/flight-level", true );
538     static SGPropertyNode *xpdr_id_code
539         = fgGetNode( "/instrumentation/transponder/outputs/id-code", true );
540
541     char digits[10];
542     int i;
543
544     if ( dme_has_power() && dme_serviceable->getBoolValue() ) {
545         if ( dme_in_range->getBoolValue() ) {
546             // DME minutes
547             float minutes = dme_min->getFloatValue();
548             if ( minutes > 999 ) {
549                 minutes = 999.0;
550             }
551             snprintf(digits, 7, "%03.0f", minutes);
552             for ( i = 0; i < 6; ++i ) {
553                 digits[i] -= '0';
554             }
555             radio_display_data[0] = digits[1] << 4 | digits[2];
556             radio_display_data[1] = 0xf0 | digits[0];
557         
558             // DME knots
559             float knots = dme_kt->getFloatValue();
560             if ( knots > 999 ) {
561                 knots = 999.0;
562             }
563             snprintf(digits, 7, "%03.0f", knots);
564             for ( i = 0; i < 6; ++i ) {
565                 digits[i] -= '0';
566             }
567             radio_display_data[2] = digits[1] << 4 | digits[2];
568             radio_display_data[3] = 0xf0 | digits[0];
569
570             // DME distance (nm)
571             float nm = dme_nm->getFloatValue();
572             if ( nm > 99 ) {
573                 nm = 99.0;
574             }
575             snprintf(digits, 7, "%04.1f", nm);
576             for ( i = 0; i < 6; ++i ) {
577                 digits[i] -= '0';
578             }
579             radio_display_data[4] = digits[1] << 4 | digits[3];
580             radio_display_data[5] = 0x00 | digits[0];
581             // the 0x00 in the upper nibble of the 6th byte of each
582             // display turns on the decimal point
583         } else {
584             // out of range
585             radio_display_data[0] = 0xbb;
586             radio_display_data[1] = 0xfb;
587             radio_display_data[2] = 0xbb;
588             radio_display_data[3] = 0xfb;
589             radio_display_data[4] = 0xbb;
590             radio_display_data[5] = 0x0b;
591         }
592     } else {
593         // blank dem display
594         for ( i = 0; i < 6; ++i ) {
595             radio_display_data[i] = 0xff;
596         }
597     }
598
599     if ( navcom1_has_power() && comm1_serviceable->getBoolValue() ) {
600         // Com1 standby frequency
601         float com1_stby = com1_stby_freq->getFloatValue();
602         if ( fabs(com1_stby) > 999.99 ) {
603             com1_stby = 0.0;
604         }
605         snprintf(digits, 7, "%06.3f", com1_stby);
606         for ( i = 0; i < 6; ++i ) {
607             digits[i] -= '0';
608         }
609         radio_display_data[6] = digits[4] << 4 | digits[5];
610         radio_display_data[7] = digits[1] << 4 | digits[2];
611         radio_display_data[8] = 0xf0 | digits[0];
612
613         // Com1 in use frequency
614         float com1 = com1_freq->getFloatValue();
615         if ( fabs(com1) > 999.99 ) {
616             com1 = 0.0;
617         }
618         snprintf(digits, 7, "%06.3f", com1);
619         for ( i = 0; i < 6; ++i ) {
620             digits[i] -= '0';
621         }
622         radio_display_data[9] = digits[4] << 4 | digits[5];
623         radio_display_data[10] = digits[1] << 4 | digits[2];
624         radio_display_data[11] = 0x00 | digits[0];
625         // the 0x00 in the upper nibble of the 6th byte of each display
626         // turns on the decimal point
627     } else {
628         radio_display_data[6] = 0xff;
629         radio_display_data[7] = 0xff;
630         radio_display_data[8] = 0xff;
631         radio_display_data[9] = 0xff;
632         radio_display_data[10] = 0xff;
633         radio_display_data[11] = 0xff;
634     }
635
636     if ( navcom2_has_power() && comm2_serviceable->getBoolValue() ) {
637         // Com2 standby frequency
638         float com2_stby = com2_stby_freq->getFloatValue();
639         if ( fabs(com2_stby) > 999.99 ) {
640             com2_stby = 0.0;
641         }
642         snprintf(digits, 7, "%06.3f", com2_stby);
643         for ( i = 0; i < 6; ++i ) {
644             digits[i] -= '0';
645         }
646         radio_display_data[18] = digits[4] << 4 | digits[5];
647         radio_display_data[19] = digits[1] << 4 | digits[2];
648         radio_display_data[20] = 0xf0 | digits[0];
649
650         // Com2 in use frequency
651         float com2 = com2_freq->getFloatValue();
652         if ( fabs(com2) > 999.99 ) {
653         com2 = 0.0;
654         }
655         snprintf(digits, 7, "%06.3f", com2);
656         for ( i = 0; i < 6; ++i ) {
657             digits[i] -= '0';
658         }
659         radio_display_data[21] = digits[4] << 4 | digits[5];
660         radio_display_data[22] = digits[1] << 4 | digits[2];
661         radio_display_data[23] = 0x00 | digits[0];
662         // the 0x00 in the upper nibble of the 6th byte of each display
663         // turns on the decimal point
664     } else {
665         radio_display_data[18] = 0xff;
666         radio_display_data[19] = 0xff;
667         radio_display_data[20] = 0xff;
668         radio_display_data[21] = 0xff;
669         radio_display_data[22] = 0xff;
670         radio_display_data[23] = 0xff;
671     }
672
673     if ( navcom1_has_power() && nav1_serviceable->getBoolValue() ) {
674         // Nav1 standby frequency
675         float nav1_stby = nav1_stby_freq->getFloatValue();
676         if ( fabs(nav1_stby) > 999.99 ) {
677         nav1_stby = 0.0;
678         }
679         snprintf(digits, 7, "%06.2f", nav1_stby);
680         for ( i = 0; i < 6; ++i ) {
681             digits[i] -= '0';
682         }
683         radio_display_data[12] = digits[4] << 4 | digits[5];
684         radio_display_data[13] = digits[1] << 4 | digits[2];
685         radio_display_data[14] = 0xf0 | digits[0];
686
687         // Nav1 in use frequency
688         float nav1 = nav1_freq->getFloatValue();
689         if ( fabs(nav1) > 999.99 ) {
690             nav1 = 0.0;
691         }
692         snprintf(digits, 7, "%06.2f", nav1);
693         for ( i = 0; i < 6; ++i ) {
694             digits[i] -= '0';
695         }
696         radio_display_data[15] = digits[4] << 4 | digits[5];
697         radio_display_data[16] = digits[1] << 4 | digits[2];
698         radio_display_data[17] = 0x00 | digits[0];
699         // the 0x00 in the upper nibble of the 6th byte of each display
700         // turns on the decimal point
701     } else {
702         radio_display_data[12] = 0xff;
703         radio_display_data[13] = 0xff;
704         radio_display_data[14] = 0xff;
705         radio_display_data[15] = 0xff;
706         radio_display_data[16] = 0xff;
707         radio_display_data[17] = 0xff;
708     }
709
710     if ( navcom2_has_power() && nav2_serviceable->getBoolValue() ) {
711         // Nav2 standby frequency
712         float nav2_stby = nav2_stby_freq->getFloatValue();
713         if ( fabs(nav2_stby) > 999.99 ) {
714             nav2_stby = 0.0;
715         }
716         snprintf(digits, 7, "%06.2f", nav2_stby);
717         for ( i = 0; i < 6; ++i ) {
718             digits[i] -= '0';
719         }
720         radio_display_data[24] = digits[4] << 4 | digits[5];
721         radio_display_data[25] = digits[1] << 4 | digits[2];
722         radio_display_data[26] = 0xf0 | digits[0];
723
724         // Nav2 in use frequency
725         float nav2 = nav2_freq->getFloatValue();
726         if ( fabs(nav2) > 999.99 ) {
727             nav2 = 0.0;
728         }
729         snprintf(digits, 7, "%06.2f", nav2);
730         for ( i = 0; i < 6; ++i ) {
731             digits[i] -= '0';
732         }
733         radio_display_data[27] = digits[4] << 4 | digits[5];
734         radio_display_data[28] = digits[1] << 4 | digits[2];
735         radio_display_data[29] = 0x00 | digits[0];
736         // the 0x00 in the upper nibble of the 6th byte of each display
737         // turns on the decimal point
738     } else {
739         radio_display_data[24] = 0xff;
740         radio_display_data[25] = 0xff;
741         radio_display_data[26] = 0xff;
742         radio_display_data[27] = 0xff;
743         radio_display_data[28] = 0xff;
744         radio_display_data[29] = 0xff;
745     }
746
747     // ADF standby frequency / timer
748     if ( adf_has_power() && adf_serviceable->getBoolValue() ) {
749         if ( adf_stby_mode->getIntValue() == 0 ) {
750             // frequency
751             float adf_stby = adf_stby_freq->getFloatValue();
752             if ( fabs(adf_stby) > 1799 ) {
753                 adf_stby = 1799;
754             }
755             snprintf(digits, 7, "%04.0f", adf_stby);
756             for ( i = 0; i < 6; ++i ) {
757                 digits[i] -= '0';
758             }
759             radio_display_data[30] = digits[3] << 4 | 0x0f;
760             radio_display_data[31] = digits[1] << 4 | digits[2];
761             if ( digits[0] == 0 ) {
762                 radio_display_data[32] = 0xff;
763             } else {
764                 radio_display_data[32] = 0xf0 | digits[0];
765             }
766         } else {
767             // timer
768             double time;
769             int hours, min, sec;
770             if ( adf_timer_mode->getIntValue() == 0 ) {
771                 time = adf_flight_timer->getDoubleValue();
772             } else {
773                 time = adf_elapsed_timer->getDoubleValue();
774             }
775             // cout << time << endl;
776             hours = (int)(time / 3600.0);
777             time -= hours * 3600.00;
778             min = (int)(time / 60.0);
779             time -= min * 60.0;
780             sec = (int)time;
781             int big, little;
782             if ( hours > 0 ) {
783                 big = hours;
784                 if ( big > 99 ) {
785                     big = 99;
786                 }
787                 little = min;
788             } else {
789                 big = min;
790                 little = sec;
791             }
792             if ( big > 99 ) {
793                 big = 99;
794             }
795             // cout << big << ":" << little << endl;
796             snprintf(digits, 7, "%02d%02d", big, little);
797             for ( i = 0; i < 6; ++i ) {
798                 digits[i] -= '0';
799             }
800             radio_display_data[30] = digits[2] << 4 | digits[3];
801             radio_display_data[31] = digits[0] << 4 | digits[1];
802             radio_display_data[32] = 0xff;
803         }
804
805         // ADF in use frequency
806         float adf = adf_freq->getFloatValue();
807         if ( fabs(adf) > 1799 ) {
808             adf = 1799;
809         }
810         snprintf(digits, 7, "%04.0f", adf);
811         for ( i = 0; i < 6; ++i ) {
812             digits[i] -= '0';
813         }
814         radio_display_data[33] = digits[2] << 4 | digits[3];
815         if ( digits[0] == 0 ) {
816             radio_display_data[34] = 0xf0 | digits[1];
817         } else {
818             radio_display_data[34] = digits[0] << 4 | digits[1];
819         }
820         if ( adf_stby_mode->getIntValue() == 0 ) {
821           radio_display_data[35] = 0xff;
822         } else {
823           radio_display_data[35] = 0x0f;
824         }
825     } else {
826         radio_display_data[30] = 0xff;
827         radio_display_data[31] = 0xff;
828         radio_display_data[32] = 0xff;
829         radio_display_data[33] = 0xff;
830         radio_display_data[34] = 0xff;
831         radio_display_data[35] = 0xff;
832     }
833     
834     // Transponder code and flight level
835     if ( xpdr_has_power() && xpdr_serviceable->getBoolValue() ) {
836         if ( xpdr_func_knob->getIntValue() == 2 ) {
837             // test mode
838             radio_display_data[36] = 8 << 4 | 8;
839             radio_display_data[37] = 8 << 4 | 8;
840             radio_display_data[38] = 0xff;
841             radio_display_data[39] = 8 << 4 | 0x0f;
842             radio_display_data[40] = 8 << 4 | 8;
843         } else {
844             // other on modes
845             int id_code = xpdr_id_code->getIntValue();
846             int place = 1000;
847             for ( i = 0; i < 4; ++i ) {
848                 digits[i] = id_code / place;
849                 id_code -= digits[i] * place;
850                 place /= 10;
851             }
852             radio_display_data[36] = digits[2] << 4 | digits[3];
853             radio_display_data[37] = digits[0] << 4 | digits[1];
854             radio_display_data[38] = 0xff;
855
856             if ( xpdr_func_knob->getIntValue() == 3 ||
857                  xpdr_func_knob->getIntValue() == 5 )
858             {
859                 // do flight level display
860                 snprintf(digits, 7, "%03d", xpdr_flight_level->getIntValue() );
861                 for ( i = 0; i < 6; ++i ) {
862                     digits[i] -= '0';
863                 }
864                 radio_display_data[39] = digits[2] << 4 | 0x0f;
865                 radio_display_data[40] = digits[0] << 4 | digits[1];
866             } else {
867                 // blank flight level display
868                 radio_display_data[39] = 0xff;
869                 radio_display_data[40] = 0xff;
870             }
871         }
872     } else {
873         // off
874         radio_display_data[36] = 0xff;
875         radio_display_data[37] = 0xff;
876         radio_display_data[38] = 0xff;
877         radio_display_data[39] = 0xff;
878         radio_display_data[40] = 0xff;
879     }
880
881     ATCSetRadios( radio_display_fd, radio_display_data );
882
883     return true;
884 }
885
886
887 /////////////////////////////////////////////////////////////////////
888 // Drive the stepper motors
889 /////////////////////////////////////////////////////////////////////
890
891 bool FGATCOutput::do_steppers() {
892     SGPropertyNode *mag_compass
893         = fgGetNode( "/instrumentation/magnetic-compass/indicated-heading-deg",
894                      true );
895
896     float diff = mag_compass->getFloatValue() - compass_position;
897     while ( diff < -180.0 ) { diff += 360.0; }
898     while ( diff >  180.0 ) { diff -= 360.0; }
899
900     int steps = (int)(diff * 4);
901     // cout << "steps = " << steps << endl;
902     if ( steps > 4 ) { steps = 4; }
903     if ( steps < -4 ) { steps = -4; }
904
905     if ( abs(steps) > 0 ) {
906         unsigned char cmd = 0x80;       // stepper command
907         if ( steps > 0 ) {
908             cmd |= 0x20;                // go up
909         } else {
910             cmd |= 0x00;                // go down
911         }
912         cmd |= abs(steps);
913
914         // sync compass_position with hardware position
915         compass_position += (float)steps / 4.0;
916
917         ATCSetStepper( stepper_fd, ATC_COMPASS_CH, cmd );
918     }
919
920     return true;
921 }
922
923
924 // process the hardware outputs.  This code assumes the calling layer
925 // will lock the hardware.
926 bool FGATCOutput::process() {
927     if ( !is_open ) {
928         SG_LOG( SG_IO, SG_ALERT, "This board has not been opened for output! "
929                 << board );
930         return false;
931     }
932
933     do_lamps();
934     do_radio_display();
935 #ifdef ATCFLIGHTSIM_HAVE_COMPASS
936     do_steppers();
937 #endif
938         
939     return true;
940 }
941
942
943 bool FGATCOutput::close() {
944
945 #if defined( unix ) || defined( __CYGWIN__ )
946
947     if ( !is_open ) {
948         return true;
949     }
950
951     int result;
952
953     result = ::close( lamps_fd );
954     if ( result == -1 ) {
955         SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
956         char msg[256];
957         snprintf( msg, 256, "Error closing %s", lamps_file );
958         perror( msg );
959         exit( -1 );
960     }
961
962     result = ::close( radio_display_fd );
963     if ( result == -1 ) {
964         SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
965         char msg[256];
966         snprintf( msg, 256, "Error closing %s", radio_display_file );
967         perror( msg );
968         exit( -1 );
969     }
970
971     result = ::close( stepper_fd );
972     if ( result == -1 ) {
973         SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
974         char msg[256];
975         snprintf( msg, 256, "Error closing %s", stepper_file );
976         perror( msg );
977         exit( -1 );
978     }
979
980 #endif
981
982     return true;
983 }