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