1 // ATC-Inputs.hxx -- Translate ATC hardware inputs to FGFS properties
3 // Written by Curtis Olson, started November 2004.
5 // Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
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.
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.
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.
28 #include <simgear/compiler.h>
30 #if defined( unix ) || defined( __CYGWIN__ )
31 # include <sys/types.h>
32 # include <sys/stat.h>
43 #include <simgear/debug/logstream.hxx>
44 #include <simgear/misc/sg_path.hxx>
46 #include <Main/fg_props.hxx>
48 #include "ATC-Inputs.hxx"
54 // Constructor: The _board parameter specifies which board to
55 // reference. Possible values are 0 or 1. The _config_file parameter
56 // specifies the location of the input config file (xml)
57 FGATCInput::FGATCInput( const int _board, const SGPath &_config_file ) :
59 ignore_flight_controls(NULL),
60 ignore_pedal_controls(NULL),
66 config = _config_file;
71 static void ATCReadAnalogInputs( int fd, unsigned char *analog_in_bytes ) {
72 #if defined( unix ) || defined( __CYGWIN__ )
74 lseek( fd, 0, SEEK_SET );
76 int result = read( fd, analog_in_bytes, ATC_ANAL_IN_BYTES );
77 if ( result != ATC_ANAL_IN_BYTES ) {
78 SG_LOG( SG_IO, SG_ALERT, "Read failed" );
85 // Read status of radio switches and knobs
86 static void ATCReadRadios( int fd, unsigned char *switch_data ) {
87 #if defined( unix ) || defined( __CYGWIN__ )
89 lseek( fd, 0, SEEK_SET );
91 int result = read( fd, switch_data, ATC_RADIO_SWITCH_BYTES );
92 if ( result != ATC_RADIO_SWITCH_BYTES ) {
93 SG_LOG( SG_IO, SG_ALERT, "Read failed" );
100 // Read switch inputs
101 static void ATCReadSwitches( int fd, unsigned char *switch_bytes ) {
102 #if defined( unix ) || defined( __CYGWIN__ )
104 lseek( fd, 0, SEEK_SET );
106 int result = read( fd, switch_bytes, ATC_SWITCH_BYTES );
107 if ( result != ATC_SWITCH_BYTES ) {
108 SG_LOG( SG_IO, SG_ALERT, "Read failed" );
115 void FGATCInput::init_config() {
116 #if defined( unix ) || defined( __CYGWIN__ )
117 if ( config.str()[0] != '/' ) {
118 // not an absolute path, prepend the standard location
120 char *envp = ::getenv( "HOME" );
121 if ( envp != NULL ) {
123 tmp.append( ".atcflightsim" );
124 tmp.append( config.str() );
128 readProperties( config.str(), globals->get_props() );
133 // Open and initialize the ATC hardware
134 bool FGATCInput::open() {
136 SG_LOG( SG_IO, SG_ALERT, "This board is already open for input! "
141 // This loads the config parameters generated by "simcal"
144 SG_LOG( SG_IO, SG_ALERT,
145 "Initializing ATC input hardware, please wait ..." );
147 snprintf( analog_in_file, 256,
148 "/proc/atcflightsim/board%d/analog_in", board );
149 snprintf( radios_file, 256,
150 "/proc/atcflightsim/board%d/radios", board );
151 snprintf( switches_file, 256,
152 "/proc/atcflightsim/board%d/switches", board );
154 #if defined( unix ) || defined( __CYGWIN__ )
156 /////////////////////////////////////////////////////////////////////
157 // Open the /proc files
158 /////////////////////////////////////////////////////////////////////
160 analog_in_fd = ::open( analog_in_file, O_RDONLY );
161 if ( analog_in_fd == -1 ) {
162 SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
164 snprintf( msg, 256, "Error opening %s", analog_in_file );
169 radios_fd = ::open( radios_file, O_RDWR );
170 if ( radios_fd == -1 ) {
171 SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
173 snprintf( msg, 256, "Error opening %s", radios_file );
178 switches_fd = ::open( switches_file, O_RDONLY );
179 if ( switches_fd == -1 ) {
180 SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
182 snprintf( msg, 256, "Error opening %s", switches_file );
189 /////////////////////////////////////////////////////////////////////
190 // Finished initing hardware
191 /////////////////////////////////////////////////////////////////////
193 SG_LOG( SG_IO, SG_ALERT,
194 "Done initializing ATC input hardware." );
198 /////////////////////////////////////////////////////////////////////
199 // Connect up to property values
200 /////////////////////////////////////////////////////////////////////
202 ignore_flight_controls
203 = fgGetNode( "/input/atcsim/ignore-flight-controls", true );
204 ignore_pedal_controls
205 = fgGetNode( "/input/atcsim/ignore-pedal-controls", true );
209 snprintf( base_name, 256, "/input/atc-board[%d]/analog-in", board );
210 analog_in_node = fgGetNode( base_name );
212 snprintf( base_name, 256, "/input/atc-board[%d]/radio-switches", board );
213 radio_in_node = fgGetNode( base_name );
215 snprintf( base_name, 256, "/input/atc-board[%d]/switches", board );
216 switches_node = fgGetNode( base_name );
222 /////////////////////////////////////////////////////////////////////
223 // Read analog inputs
224 /////////////////////////////////////////////////////////////////////
226 // scale a number between min and max (with center defined) to a scale
227 // from -1.0 to 1.0. The deadband value is symmetric, so specifying
228 // '1' will give you a deadband of +/-1
229 static double scale( int center, int deadband, int min, int max, int value ) {
230 // cout << center << " " << min << " " << max << " " << value << " ";
234 if ( value <= (center - deadband) ) {
235 range = (center - deadband) - min;
236 result = (value - (center - deadband)) / range;
237 } else if ( value >= (center + deadband) ) {
238 range = max - (center + deadband);
239 result = (value - (center + deadband)) / range;
244 if ( result < -1.0 ) result = -1.0;
245 if ( result > 1.0 ) result = 1.0;
247 // cout << result << endl;
253 // scale a number between min and max to a scale from 0.0 to 1.0
254 static double scale( int min, int max, int value ) {
255 // cout << center << " " << min << " " << max << " " << value << " ";
260 result = (value - min) / range;
262 if ( result < 0.0 ) result = 0.0;
263 if ( result > 1.0 ) result = 1.0;
265 // cout << result << endl;
271 static double clamp( double min, double max, double value ) {
272 double result = value;
274 if ( result < min ) result = min;
275 if ( result > max ) result = max;
277 // cout << result << endl;
283 static int tony_magic( int raw, int obs[3] ) {
289 if ( obs[2] >= 68 && obs[2] < 480 ) {
291 } else if ( obs[2] >= 480 ) {
296 } else if ( obs[1] < 68 ) {
299 } else if ( obs[2] < 30 ) {
300 if ( obs[1] >= 68 && obs[1] < 480 ) {
304 } else if ( obs[1] >= 480 ) {
306 if ( obs[0] < obs[1] ) {
314 } else if ( obs[1] > 980 ) {
315 if ( obs[2] <= 956 && obs[2] > 480 ) {
317 } else if ( obs[2] <= 480 ) {
322 } else if ( obs[1] > 956 ) {
325 } else if ( obs[2] > 980 ) {
326 if ( obs[1] <= 956 && obs[1] > 480 ) {
330 } else if ( obs[1] <= 480 ) {
332 if ( obs[0] > obs[1] ) {
341 if ( obs[1] < 480 && obs[2] > 480 ) {
342 // crossed gap going up
343 if ( obs[0] < obs[1] ) {
344 // caught a bogus intermediate value coming out of the gap
347 } else if ( obs[1] > 480 && obs[2] < 480 ) {
348 // crossed gap going down
349 if ( obs[0] > obs[1] ) {
350 // caught a bogus intermediate value coming out of the gap
353 } else if ( obs[0] > 480 && obs[1] < 480 && obs[2] < 480 ) {
354 // crossed the gap going down
355 if ( obs[1] > obs[2] ) {
356 // caught a bogus intermediate value coming out of the gap
359 } else if ( obs[0] < 480 && obs[1] > 480 && obs[2] > 480 ) {
360 // crossed the gap going up
361 if ( obs[1] < obs[2] ) {
362 // caught a bogus intermediate value coming out of the gap
366 result = obs[1] - obs[2];
367 if ( abs(result) > 400 ) {
375 // cout << " result = " << result << endl;
376 if ( result < -500 ) { result += 1024; }
377 if ( result > 500 ) { result -= 1024; }
383 static double instr_pot_filter( double ave, double val ) {
384 if ( fabs(ave - val) < 400 || fabs(val) < fabs(ave) ) {
385 return 0.5 * ave + 0.5 * val;
392 bool FGATCInput::do_analog_in() {
393 // Read raw data in byte form
394 ATCReadAnalogInputs( analog_in_fd, analog_in_bytes );
396 // Convert to integer values
397 for ( int channel = 0; channel < ATC_ANAL_IN_VALUES; ++channel ) {
398 unsigned char hi = analog_in_bytes[2 * channel] & 0x03;
399 unsigned char lo = analog_in_bytes[2 * channel + 1];
400 analog_in_data[channel] = hi * 256 + lo;
402 // printf("%02x %02x ", hi, lo );
403 // printf("%04d ", value );
406 // Process analog inputs
407 if ( analog_in_node != NULL ) {
408 for ( int i = 0; i < analog_in_node->nChildren(); ++i ) {
409 // read the next config entry from the property tree
411 SGPropertyNode *child = analog_in_node->getChild(i);
412 string cname = child->getName();
413 int index = child->getIndex();
417 vector <SGPropertyNode *> output_nodes; output_nodes.clear();
424 if ( cname == "channel" ) {
425 SGPropertyNode *prop;
426 prop = child->getChild( "name" );
427 if ( prop != NULL ) {
428 name = prop->getStringValue();
430 prop = child->getChild( "type", 0 );
431 if ( prop != NULL ) {
432 type = prop->getStringValue();
434 prop = child->getChild( "type", 1 );
435 if ( prop != NULL ) {
436 subtype = prop->getStringValue();
439 while ( (prop = child->getChild("prop", j)) != NULL ) {
441 = fgGetNode( prop->getStringValue(), true );
442 output_nodes.push_back( tmp );
445 prop = child->getChild( "center" );
446 if ( prop != NULL ) {
447 center = prop->getIntValue();
449 prop = child->getChild( "min" );
450 if ( prop != NULL ) {
451 min = prop->getIntValue();
453 prop = child->getChild( "max" );
454 if ( prop != NULL ) {
455 max = prop->getIntValue();
457 prop = child->getChild( "deadband" );
458 if ( prop != NULL ) {
459 deadband = prop->getIntValue();
461 prop = child->getChild( "offset" );
462 if ( prop != NULL ) {
463 offset = prop->getFloatValue();
465 prop = child->getChild( "factor" );
466 if ( prop != NULL ) {
467 factor = prop->getFloatValue();
470 // Fetch the raw value
472 int raw_value = analog_in_data[index];
474 // Update the target properties
476 if ( type == "flight"
477 && !ignore_flight_controls->getBoolValue() )
479 if ( subtype != "pedals" ||
480 ( subtype == "pedals"
481 && !ignore_pedal_controls->getBoolValue() ) )
483 // "Cook" the raw value
484 float scaled_value = 0.0f;
486 scaled_value = scale( center, deadband,
487 min, max, raw_value );
489 scaled_value = scale( min, max, raw_value );
491 scaled_value *= factor;
492 scaled_value += offset;
494 // final sanity clamp
496 scaled_value = clamp( -1.0, 1.0, scaled_value );
498 scaled_value = clamp( 0.0, 1.0, scaled_value );
501 // update the property tree values
502 for ( j = 0; j < (int)output_nodes.size(); ++j ) {
503 output_nodes[j]->setDoubleValue( scaled_value );
506 } else if ( type == "avionics-simple" ) {
507 // "Cook" the raw value
508 float scaled_value = 0.0f;
510 scaled_value = scale( center, deadband,
511 min, max, raw_value );
513 scaled_value = scale( min, max, raw_value );
515 scaled_value *= factor;
516 scaled_value += offset;
518 // final sanity clamp
520 scaled_value = clamp( -1.0, 1.0, scaled_value );
522 scaled_value = clamp( 0.0, 1.0, scaled_value );
525 // update the property tree values
526 for ( j = 0; j < (int)output_nodes.size(); ++j ) {
527 output_nodes[j]->setDoubleValue( scaled_value );
529 } else if ( type == "avionics-resolver" ) {
530 // this type of analog input impliments a
531 // rotational knob. We first caclulate the amount
532 // of knob rotation (slightly complex to work with
533 // hardware specific goofiness) and then multiply
534 // that amount of movement by a scaling factor,
535 // and finally add the result to the original
538 bool do_init = false;
539 float scaled_value = 0.0f;
541 // fetch intermediate values from property tree
543 prop = child->getChild( "is-inited", 0 );
544 if ( prop == NULL ) {
546 prop = child->getChild( "is-inited", 0, true );
547 prop->setBoolValue( true );
551 for ( j = 0; j < 3; ++j ) {
552 prop = child->getChild( "raw", j, true );
554 raw[j] = analog_in_data[index];
556 raw[j] = prop->getIntValue();
560 // do Tony's magic to calculate knob movement
561 // based on current analog input position and
563 int diff = tony_magic( analog_in_data[index], raw );
565 // write raw intermediate values (updated by
566 // tony_magic()) back to property tree
567 for ( j = 0; j < 3; ++j ) {
568 prop = child->getChild( "raw", j, true );
569 prop->setIntValue( raw[j] );
572 // filter knob position
573 prop = child->getChild( "diff-average", 0, true );
574 double diff_ave = prop->getDoubleValue();
575 diff_ave = instr_pot_filter( diff_ave, diff );
576 prop->setDoubleValue( diff_ave );
578 // calculate value adjustment in real world units
579 scaled_value = diff_ave * factor;
581 // update the property tree values
582 for ( j = 0; j < (int)output_nodes.size(); ++j ) {
583 float value = output_nodes[j]->getDoubleValue();
584 value += scaled_value;
586 prop = child->getChild( "min-clamp" );
587 if ( prop != NULL ) {
588 double min = prop->getDoubleValue();
589 if ( value < min ) { value = min; }
592 prop = child->getChild( "max-clamp" );
593 if ( prop != NULL ) {
594 double max = prop->getDoubleValue();
595 if ( value > max ) { value = max; }
598 prop = child->getChild( "compass-heading" );
599 if ( prop != NULL ) {
600 bool compass = prop->getBoolValue();
602 while ( value >= 360.0 ) { value -= 360.0; }
603 while ( value < 0.0 ) { value += 360.0; }
607 output_nodes[j]->setDoubleValue( value );
611 SG_LOG( SG_IO, SG_DEBUG, "Invalid channel type = "
615 SG_LOG( SG_IO, SG_DEBUG,
616 "Input config error, expecting 'channel' but found "
626 /////////////////////////////////////////////////////////////////////
627 // Read the switch positions
628 /////////////////////////////////////////////////////////////////////
630 // decode the packed switch data
631 static void update_switch_matrix(
633 unsigned char switch_data[ATC_SWITCH_BYTES],
634 int switch_matrix[2][ATC_NUM_COLS][ATC_SWITCH_BYTES] )
636 for ( int row = 0; row < ATC_SWITCH_BYTES; ++row ) {
637 unsigned char switches = switch_data[row];
639 for( int column = 0; column < ATC_NUM_COLS; ++column ) {
640 switch_matrix[board][column][row] = switches & 1;
641 switches = switches >> 1;
646 bool FGATCInput::do_switches() {
648 ATCReadSwitches( switches_fd, switch_data );
650 // unpack the switch data
651 int switch_matrix[2][ATC_NUM_COLS][ATC_SWITCH_BYTES];
652 update_switch_matrix( board, switch_data, switch_matrix );
654 // Process the switch inputs
655 if ( switches_node != NULL ) {
656 for ( int i = 0; i < switches_node->nChildren(); ++i ) {
657 // read the next config entry from the property tree
659 SGPropertyNode *child = switches_node->getChild(i);
660 string cname = child->getName();
663 vector <SGPropertyNode *> output_nodes; output_nodes.clear();
668 float scaled_value = 0.0f;
671 // get common options
673 SGPropertyNode *prop;
674 prop = child->getChild( "name" );
675 if ( prop != NULL ) {
676 name = prop->getStringValue();
678 prop = child->getChild( "type" );
679 if ( prop != NULL ) {
680 type = prop->getStringValue();
683 while ( (prop = child->getChild("prop", j)) != NULL ) {
685 = fgGetNode( prop->getStringValue(), true );
686 output_nodes.push_back( tmp );
689 prop = child->getChild( "factor" );
690 if ( prop != NULL ) {
691 factor = prop->getFloatValue();
693 prop = child->getChild( "invert" );
694 if ( prop != NULL ) {
695 invert = prop->getBoolValue();
697 prop = child->getChild( "steady-state-filter" );
698 if ( prop != NULL ) {
699 filter = prop->getIntValue();
702 // handle different types of switches
704 if ( cname == "switch" ) {
705 prop = child->getChild( "row" );
706 if ( prop != NULL ) {
707 row = prop->getIntValue();
709 prop = child->getChild( "col" );
710 if ( prop != NULL ) {
711 col = prop->getIntValue();
714 // Fetch the raw value
715 int raw_value = switch_matrix[board][row][col];
719 raw_value = !raw_value;
723 scaled_value = (float)raw_value * factor;
725 } else if ( cname == "combo-switch" ) {
726 float combo_value = 0.0f;
730 while ( (pos = child->getChild("position", k++)) != NULL ) {
731 // read the combo position entries from the property tree
733 prop = pos->getChild( "row" );
734 if ( prop != NULL ) {
735 row = prop->getIntValue();
737 prop = pos->getChild( "col" );
738 if ( prop != NULL ) {
739 col = prop->getIntValue();
741 prop = pos->getChild( "value" );
742 if ( prop != NULL ) {
743 combo_value = prop->getFloatValue();
746 // Fetch the raw value
747 int raw_value = switch_matrix[board][row][col];
748 // cout << "sm[" << board << "][" << row << "][" << col
749 // << "] = " << raw_value << endl;
752 // set scaled_value to the first combo_value
753 // that matches and jump out of loop.
754 scaled_value = combo_value;
760 scaled_value *= factor;
761 } else if ( cname == "additive-switch" ) {
762 float additive_value = 0.0f;
763 float increment = 0.0f;
767 while ( (pos = child->getChild("position", k++)) != NULL ) {
768 // read the combo position entries from the property tree
770 prop = pos->getChild( "row" );
771 if ( prop != NULL ) {
772 row = prop->getIntValue();
774 prop = pos->getChild( "col" );
775 if ( prop != NULL ) {
776 col = prop->getIntValue();
778 prop = pos->getChild( "value" );
779 if ( prop != NULL ) {
780 increment = prop->getFloatValue();
783 // Fetch the raw value
784 int raw_value = switch_matrix[board][row][col];
785 // cout << "sm[" << board << "][" << row << "][" << col
786 // << "] = " << raw_value << endl;
789 // set scaled_value to the first combo_value
790 // that matches and jump out of loop.
791 additive_value += increment;
796 scaled_value = additive_value * factor;
799 // handle filter request. The value of the switch must be
800 // steady-state for "n" frames before the property value
803 bool update_prop = true;
806 SGPropertyNode *fv = child->getChild( "filter-value", 0, true );
807 float filter_value = fv->getFloatValue();
808 SGPropertyNode *fc = child->getChild( "filter-count", 0, true );
809 int filter_count = fc->getIntValue();
811 if ( fabs(scaled_value - filter_value) < 0.0001 ) {
817 if ( filter_count < filter ) {
821 fv->setFloatValue( scaled_value );
822 fc->setIntValue( filter_count );
826 if ( type == "engine" || type == "flight" ) {
827 if ( ! ignore_flight_controls->getBoolValue() ) {
828 // update the property tree values
829 for ( j = 0; j < (int)output_nodes.size(); ++j ) {
830 output_nodes[j]->setDoubleValue( scaled_value );
833 } else if ( type == "avionics" ) {
834 // update the property tree values
835 for ( j = 0; j < (int)output_nodes.size(); ++j ) {
836 output_nodes[j]->setDoubleValue( scaled_value );
847 /////////////////////////////////////////////////////////////////////
848 // Read radio switches
849 /////////////////////////////////////////////////////////////////////
851 bool FGATCInput::do_radio_switches() {
853 ATCReadRadios( radios_fd, radio_switch_data );
855 // Process the radio switch/knob inputs
856 if ( radio_in_node != NULL ) {
857 for ( int i = 0; i < radio_in_node->nChildren(); ++i ) {
858 // read the next config entry from the property tree
860 SGPropertyNode *child = radio_in_node->getChild(i);
861 string cname = child->getName();
863 if ( cname == "switch" ) {
866 vector <SGPropertyNode *> output_nodes; output_nodes.clear();
873 int scaled_value = 0;
874 // get common options
876 SGPropertyNode *prop;
877 prop = child->getChild( "name" );
878 if ( prop != NULL ) {
879 name = prop->getStringValue();
881 prop = child->getChild( "type" );
882 if ( prop != NULL ) {
883 type = prop->getStringValue();
886 while ( (prop = child->getChild("prop", j)) != NULL ) {
888 = fgGetNode( prop->getStringValue(), true );
889 output_nodes.push_back( tmp );
892 prop = child->getChild( "byte" );
893 if ( prop != NULL ) {
894 byte_num = prop->getIntValue();
896 prop = child->getChild( "right-shift" );
897 if ( prop != NULL ) {
898 right_shift = prop->getIntValue();
900 prop = child->getChild( "mask" );
901 if ( prop != NULL ) {
902 mask = prop->getIntValue();
904 prop = child->getChild( "factor" );
905 if ( prop != NULL ) {
906 factor = prop->getIntValue();
908 prop = child->getChild( "offset" );
909 if ( prop != NULL ) {
910 offset = prop->getIntValue();
912 prop = child->getChild( "invert" );
913 if ( prop != NULL ) {
914 invert = prop->getBoolValue();
917 // Fetch the raw value
919 = (radio_switch_data[byte_num] >> right_shift) & mask;
923 raw_value = !raw_value;
925 scaled_value = raw_value * factor + offset;
927 // update the property tree values
928 for ( j = 0; j < (int)output_nodes.size(); ++j ) {
929 output_nodes[j]->setIntValue( scaled_value );
939 // process the hardware inputs. This code assumes the calling layer
940 // will lock the hardware.
941 bool FGATCInput::process() {
943 SG_LOG( SG_IO, SG_ALERT, "This board has not been opened for input! "
956 bool FGATCInput::close() {
958 #if defined( unix ) || defined( __CYGWIN__ )
966 result = ::close( analog_in_fd );
967 if ( result == -1 ) {
968 SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
970 snprintf( msg, 256, "Error closing %s", analog_in_file );
975 result = ::close( radios_fd );
976 if ( result == -1 ) {
977 SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
979 snprintf( msg, 256, "Error closing %s", radios_file );
984 result = ::close( switches_fd );
985 if ( result == -1 ) {
986 SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
988 snprintf( msg, 256, "Error closing %s", switches_file );