Joystick autopilot bindings

From FlightGear wiki
Revision as of 21:07, 26 September 2014 by Wonko (talk | contribs) (Completion first draft)
Jump to navigation Jump to search

Introduction

Before you start, read Input device and probably Writing Joystick Code: Part 1

Autopilots are generally very useful features, but unfortunately there are only key bindings for the `magic autopilot', and the only way to control popular GA autopilots is by clicking the corresponding buttons in the 3D-cockpit. This is awkward if there is a need to change mode or turn off the autopilot quickly (at final stage of ILS landing, autopilot does something silly). This page describes joystick bindings, which can be used to control the (simulated) autopilot with a set of joystick buttons. Every type of autopilot is operated quite differently, such that these keys only work for specific models. In the current implementation the key bindings work for the Bendix/King KAP140 Autopilot (in the Cessna 172) and the CENTURY III Autopilot POH (Seneca II). Those were prioritised, as the more complex autopilots in jets usually have a dedicated GUI but it is planned to provide bindings for at least basic functions like AUTOPILOT OFF in the future.

Implementation

The example code provides a nice setup for six buttons arranged like this (this happens to be suitable for the Thrustmaster T16000M but it can be adapted trivially to other button numbers by simply changing the button numbers in the snippets below. THis is the arrangement of buttons in the T16000M:

4 5 6
9 8 7

In the following snippets for each functionality are given - usually one with the actual binding and some corresponding initialisation code (put between nasal tags at the beginning of the script). You need to copy/paste both into your joystick bindings xml file. In the summary the code of all snippets is provided in one code box for convenience but you learn more if you consider each snippet in turn. Generally, it was attempted to have somewhat consistent bindings between the different autopilot types, but because they are operated quite differently, this was not entirely possible, and it was attempted to stay close to the mode of operation of the actual instruments.

Generic initialisation code

On startup, we need to figure out whether we are dealing with a Bendix KAP 140 or CENTURY II autopilot.

<nasal>
    <!-- set autopilot type:
          1: KAP140     (e.g. c172p)
          2: CENTURYIII (e.g. senecaII) -->
   <script><![CDATA[
      var autopilottype=0;
      var horizontalmode=0;
     if (getprop("sim/aircraft")=="c172p") {
        print("FOUND KAP140 autopilot");
        autopilottype=1;
     } elsif (getprop("autopilot/CENTURYIII/power")>-1) {
        print("Using CENTURYIII autopilot joystick binding");
        autopilottype=2; 
     }
    ....
   ]]></script>
</nasal>

As you can see it assumes only the Cessna 172 has the KAP-140. If there is another plane with this autopilot the condition should be adjusted.

Major mode selection

Button 4 turns the autopilot on or off (toggle switch). With modifiers:
(SHIFT) HDG mode toggle (the details of this mode are controlled by button 4)
(CTRL) PITCH mode toggle (CENTURYIII only!)
(ALT) ALT mode toggle (KAP140: switches between VS and ALT vertical modes. CENTURYIII: turns on and off ALT mode)

Button settings:

<button n="4">
    <repeatable type="bool">false</repeatable>
    <desc>Autopilot Toggle / HDG mode toggle / PITCH mode / ALT mode </desc>
   <binding>
      <condition><property>/autopilot/CENTURYIII/controls/roll/button-state</property></condition>
       <command>property-assign</command>
      <property>autopilot/CENTURYIII/controls/disconnect</property>
      <value>1</value>
     </binding>
    <binding>
      <condition><not><property>/autopilot/CENTURYIII/controls/roll/button-state</property></not></condition>
       <command>property-assign</command>
      <property>autopilot/CENTURYIII/controls/roll/button-on</property>
      <value>1</value>
     </binding>
      <binding>
      <command>nasal</command>
      <script>
       if ( autopilottype == 1 ) { 
         kap140.apButton();
         if ( getprop("autopilot/KAP140/locks/roll-mode") == 0 ) {
           gui.popupTip("AP off");
         } else {
           gui.popupTip("AP on");
         }
       } 
      </script>
    </binding>
    <mod-up>
      <binding>
        <command>property-assign</command>
        <property>autopilot/CENTURYIII/controls/disconnect</property>
        <value>0</value>
      </binding>
     <binding>
        <command>property-assign</command>
        <property>autopilot/CENTURYIII/controls/roll/button-on</property>
        <value>0</value>
      </binding>
        <binding>
        <command>nasal</command><script>
        if ( autopilottype == 2 ) { 
          <!-- CENTURYIII -->
          if ( getprop("autopilot/CENTURYIII/controls/roll/button-state") ) {
             gui.popupTip("AP ROLL on");
           } else {
             gui.popupTip("AP ROLL off");
           } 
         }
       </script>
       </binding>
    </mod-up> 
  <mod-shift> 
   <binding>
      <condition><property>/autopilot/CENTURYIII/controls/hdg/button-state</property></condition>
       <command>property-assign</command>
      <property>autopilot/CENTURYIII/controls/hdg/button-off</property>
      <value>1</value>
     </binding>
    <binding>
      <condition><not><property>/autopilot/CENTURYIII/controls/hdg/button-state</property></not></condition>
       <command>property-assign</command>
      <property>autopilot/CENTURYIII/controls/hdg/button-on</property>
      <value>1</value>
     </binding>
    <binding>
      <command>nasal</command>
      <script>
       if ( autopilottype == 1 ) { 
         kap140.hdgButton();
         if ( getprop("autopilot/KAP140/locks/roll-mode") == 2 ) {
           gui.popupTip("AP HDG");
         } elsif ( getprop("autopilot/KAP140/locks/roll-mode") == 1 )   {
           gui.popupTip("AP ROL");
         }
       } 
      </script>
    </binding>
   <mod-up>
      <binding>
        <command>property-assign</command>
        <property>autopilot/CENTURYIII/controls/hdg/button-off</property>
        <value>0</value>
      </binding>
     <binding>
        <command>property-assign</command>
        <property>autopilot/CENTURYIII/controls/hdg/button-on</property>
        <value>0</value>
      </binding>
      <binding>
      <command>nasal</command><script>
       if ( autopilottype == 2 ) { 
          <!-- CENTURYIII -->
         if ( getprop("autopilot/CENTURYIII/controls/hdg/button-state") ) {
           gui.popupTip("AP HDG on");
         } else {
           gui.popupTip("AP HDG off");
         }
        }
      </script>
     </binding>
    </mod-up> 
  </mod-shift>
  <mod-ctrl> 
   <binding>
      <condition><property>/autopilot/CENTURYIII/controls/pitch/button-state</property></condition>
       <command>property-assign</command>
      <property>autopilot/CENTURYIII/controls/pitch/button-off</property>
      <value>1</value>
     </binding>
    <binding>
      <condition><not><property>/autopilot/CENTURYIII/controls/pitch/button-state</property></not></condition>
       <command>property-assign</command>
      <property>autopilot/CENTURYIII/controls/pitch/button-on</property>
      <value>1</value>
     </binding>
   <mod-up>
      <binding>
        <command>property-assign</command>
        <property>autopilot/CENTURYIII/controls/pitch/button-off</property>
        <value>0</value>
      </binding>
     <binding>
        <command>property-assign</command>
        <property>autopilot/CENTURYIII/controls/pitch/button-on</property>
        <value>0</value>
      </binding>
        <binding>
        <command>nasal</command><script>
        if ( autopilottype == 2 ) { 
          <!-- CENTURYIII -->
          if ( getprop("autopilot/CENTURYIII/controls/pitch/button-state") ) {
             gui.popupTip("AP PITCH on");
           } else {
             gui.popupTip("AP PITCH off");
           } 
         }
        </script>
      </binding>
    </mod-up> 
  </mod-ctrl>
  <mod-alt> 
    <binding>
      <command>nasal</command>
      <script>if (autopilottype==1 ) {
       kap140.altButton();
       if ( getprop("autopilot/KAP140/locks/pitch-mode") == 2 ) {
         gui.popupTip("AP ALT");
       } elsif ( getprop("autopilot/KAP140/locks/pitch-mode") == 1 )   {
         gui.popupTip("AP VS");
       }
       }
    </script>
    </binding>
   <binding>
      <condition><property>/autopilot/CENTURYIII/controls/alt/button-state</property></condition>
       <command>property-assign</command>
      <property>autopilot/CENTURYIII/controls/alt/button-off</property>
      <value>true</value>
     </binding>
    <binding>
      <condition><not><property>/autopilot/CENTURYIII/controls/alt/button-state</property></not></condition>
       <command>property-assign</command>
      <property>autopilot/CENTURYIII/controls/alt/button-on</property>
      <value>true</value>
     </binding>
   <mod-up>
      <binding>
        <command>property-assign</command>
        <property>autopilot/CENTURYIII/controls/alt/button-off</property>
        <value>false</value>
      </binding>
     <binding>
        <command>property-assign</command>
        <property>autopilot/CENTURYIII/controls/alt/button-on</property>
        <value>false</value>
      </binding>
      <binding>
        <command>nasal</command>
        <script>
           if(autopilottype==2) {
              if ( getprop("autopilot/CENTURYIII/controls/alt/button-state")) {
                  gui.popupTip("AP ALT on");
              } else {
                  gui.popupTip("AP ALT off");
              }
           }
        </script>
     </binding>
    </mod-up> 
  </mod-alt>
</button>

Horizontal mode selection

Button 5 controls the source of course selection in HDG mode. It works quite differently in the two autopilots:

  • KAP140: (no modifier) NAV track radial of VOR1 beacon. Press again to go back to follow set heading

(SHIFT) APR mode: track ILS beacon (set radial to runway orientation). If glideslope is approached from below in ALT mode, lock on to localiser
(CTRL) REV mode: track reverse ILS direction

  • CENTURYIII: Move mode selector button to the right (clockwise)

(SHIFT) Move mode selector button to the left (anti-clockwise)
Both direction are wrapped, i.e. you can keep pressing the button until you arrive at the selected mode. In turn the modes selected are: NAV, OMNI, HDG, LOC NORM, LOC REV. Please refer to the CENTURYIII manual for further explanation.

Initialisation:

     var showhorizontalmode=func() {
     if ( autopilottype == 1 ) {
       if ( getprop("autopilot/KAP140/locks/roll-arm") == 1 ) {
         gui.popupTip("AP NAV");
       } elsif ( getprop("autopilot/KAP140/locks/roll-arm") == 2 ) {
         gui.popupTip("AP APR");
       } elsif ( getprop("autopilot/KAP140/locks/roll-arm") == 3 ) {
         gui.popupTip("AP REV");
       }
     }
     if (autopilottype == 2) {
        horizontalmode=getprop("autopilot/CENTURYIII/controls/mode");
        if ( horizontalmode==0) {
          gui.popupTip("APmode NAV");
        } elsif ( horizontalmode==1) {
          gui.popupTip("APmode OMNI");
        } elsif ( horizontalmode==2) {
          gui.popupTip("APmode HDG");
        } elsif ( horizontalmode==3) {
          gui.popupTip("APmode LOC");
        } elsif ( horizontalmode==4) {
          gui.popupTip("APmode REV");
        } 
     }
    }

Button settings:

<button n="5">
    <repeatable type="bool">false</repeatable>
    <desc>KAP140: NAV / APR / REV ; CENTURYIII:
Move Hdg select button to the right (wrap)</desc>
    <binding>
           <command>property-adjust</command>
            <property>autopilot/CENTURYIII/controls/mode</property>
            <step type="double">1</step>
     <min>0</min>
      <max>5</max>    
      <wrap>true</wrap> 
    </binding>
    <binding>
      <command>nasal</command>
      <script>
       if (autopilottype==1) { 
         kap140.navButton(); }
       showhorizontalmode();
     </script>
    </binding>
  <mod-shift> 
    <binding>
           <command>property-adjust</command>
            <property>autopilot/CENTURYIII/controls/mode</property>
            <step type="double">-1</step>
     <min>0</min>
      <max>5</max>    
      <wrap>true</wrap> 
    </binding>
    <binding>
      <command>nasal</command>
      <script>       
        if (autopilottype==1) { kap140.aprButton();}
       showhorizontalmode();
      </script>
    </binding>
  </mod-shift>
  <mod-ctrl> 
    <binding>
      <command>nasal</command>
      <script>        if (autopilottype==1) { kap140.revButton();}
       showhorizontalmode();
     </script>
    </binding>
  </mod-ctrl>
</button>

Vertical mode pitch or vertical speed selector

Control the vertical mode.

  • KAP140: Button 6 increases target VS by 100 ft/min, button 7 decreases target VS by 100 ft/min
  • CENTURYIII: Button 6 smoothly lowers pitch (nose down), button 7 increases pitch (nose up)

The opposite way these buttons work for the two types of autopilot arises from the arrangement in the cockpit. For the CENTURYIII, the pitch is controlled by a wheel, which needs to be moved up to lower the nose (similar to elevator trim wheels), whereas for the KAP140 the top button increases VS.

Initialisation:

     var showvertical=func()  {if ( autopilottype==1 ) {
            if ( getprop("autopilot/KAP140/locks/pitch-mode") == 1 )  {
               gui.popupTip(sprintf("AP VS
%d",getprop("autopilot/KAP140/settings/target-pressure-rate")*(-58000)));
            }
         } elsif ( autopilottype==2 ) {
            gui.popupTip(sprintf("AP Pitch:%4.1f",getprop("autopilot/CENTURYIII/settings/pitch-wheel-deg")));
         }}

Button settings:

  <button n="6">
<repeatable type="bool">false</repeatable>
    <desc>AP-VS (KAP140:increase VS, CENTURYII: pitch down</desc>
<!-- KAP140 -->
    <binding>
      <command>nasal</command>
      <script>if (autopilottype==1) {
         kap140.upButton();
      }
      showvertical(); 
     </script>
    </binding>
<!-- CENTURYIII -->
    <binding>
      <command>property-interpolate</command>
       <property>/autopilot/CENTURYIII/settings/pitch-wheel-deg</property>
       <value type="double">-10</value>
       <rate>4</rate>
     </binding>
     <mod-up>
    <binding>
      <command>property-interpolate</command>
       <property>/autopilot/CENTURYIII/settings/pitch-wheel-deg</property>
       <property>/autopilot/CENTURYIII/settings/pitch-wheel-deg</property>
       <rate>1</rate>
     </binding>
    <binding>
      <command>nasal</command>
      <script>
        if(autopilottype==2) { showvertical(); } 
     </script>
    </binding>
     </mod-up>
  </button>


  #7: Autopilot VS down
  <button n="7">
<repeatable type="bool">false</repeatable>
    <desc>AP-dn</desc>
<!-- KAP140 -->
    <binding>
      <command>nasal</command>
      <script>if (autopilottype==1) {
         kap140.downButton();
      }
      showvertical(); 
     </script>
    </binding>
<!-- CENTURYIII -->
    <binding>
      <command>property-interpolate</command>
       <property>/autopilot/CENTURYIII/settings/pitch-wheel-deg</property>
       <value type="double">10</value>
       <rate>4</rate>
     </binding>
     <mod-up>
    <binding>
      <command>property-interpolate</command>
       <property>/autopilot/CENTURYIII/settings/pitch-wheel-deg</property>
       <property>/autopilot/CENTURYIII/settings/pitch-wheel-deg</property>
       <rate>1</rate>
     </binding>
    <binding>
      <command>nasal</command>
      <script>
        if(autopilottype==2) { showvertical(); } 
     </script>
    </binding>
     </mod-up>
  </button>

Horizontal mode direction selector

Adjust heading bug (button 8 clockwise and button 9 anti-clockwise)

With modifiers:
(SHIFT) coarse heading bug adjustment
(CTRL) adjust bank angle (CENTURY II only!)
(ALT) adjust radial of NAV1 (can also be used without autopilot)


Initialisation:

    var showhdgbug=func() {if ( autopilottype==1 ) {
                 gui.popupTip(sprintf("Heading Bug:%3d",getprop("autopilot/settings/heading-bug-deg"))); 
              } elsif ( autopilottype==2 ) {
                gui.popupTip(sprintf("Hdg Bug:%3d",getprop("instrumentation/kcs55/ki525/selected-heading-deg")));
              }}
     var showroll=func() { if ( autopilottype==2 ) {
                gui.popupTip(sprintf("AP Roll:%2d",getprop("autopilot/CENTURYIII/settings/roll-knob-deg")));}}
     var showcourseselect=func() {
                if ( autopilottype==1 ) {
                  gui.popupTip(sprintf("Course select:%2d",getprop("instrumentation/nav/radials/selected-deg")));
                } elsif ( autopilottype==2 ) {
                  gui.popupTip(sprintf("Course select:%2d",getprop("instrumentation/kcs55/ki525/selected-course-deg")));}}

Button definition:

<button n="8">
    <desc>Heading bug CW / Heading bug CW large steps /
(CENTURYIII:)Roll knob turn right / Course select CW</desc>
      <repeatable>true</repeatable>
    <binding>
            <command>property-adjust</command>
            <property>/autopilot/settings/heading-bug-deg</property>
            <step type="double">0.25</step>
    <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
    <binding>
            <command>property-adjust</command>
            <property>instrumentation/kcs55/ki525/selected-heading-deg</property>
            <step type="double">0.25</step>
     <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
         <binding>
            <command>nasal</command>
             <script>
             showhdgbug();
          </script>
         </binding>
    <mod-shift>
        <binding>
            <command>nasal</command>
             <script>
             showhdgbug();
             </script>
         </binding>
    <binding>
            <command>property-adjust</command>
            <property>/autopilot/settings/heading-bug-deg</property>
            <step type="double">2.5</step>
      <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
    <binding>
            <command>property-adjust</command>
            <property>instrumentation/kcs55/ki525/selected-heading-deg</property>
            <step type="double">2.5</step>
     <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
         <binding>
            <command>nasal</command>
             <script>
              showhdgbug();
          </script>
    </binding>
    </mod-shift>
   <mod-ctrl>
    <binding>
      <command>property-adjust</command>
       <property>/autopilot/CENTURYIII/settings/roll-knob-deg</property>
       <step type="double">0.5</step>
       <max>30</max>
     </binding>
     <binding>
            <command>nasal</command>
             <script>
             showroll();
          </script>
     </binding>
   </mod-ctrl>
 <mod-alt>
   <binding>
            <command>property-adjust</command>
            <property>instrumentation/nav/radials/selected-deg</property>
            <step type="double">0.25</step>
     <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
    <binding>
            <command>property-adjust</command>
            <property>instrumentation/kcs55/ki525/selected-course-deg</property>
            <step type="double">0.25</step>
     <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
     <binding>
            <command>nasal</command>
             <script>
             showcourseselect();
          </script>
     </binding>
 </mod-alt>
  </button>


 <button n="9">
    <desc>Heading bug CCW / Heading bug CCW large steps</desc>
      <repeatable>true</repeatable>
    <binding>
            <command>property-adjust</command>
            <property>/autopilot/settings/heading-bug-deg</property>
            <step type="double">-0.25</step>
         </binding>
    <binding>
            <command>property-adjust</command>
            <property>instrumentation/kcs55/ki525/selected-heading-deg</property>
            <step type="double">-0.25</step>
     <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
         <binding>
            <command>nasal</command>
             <script>
                 showhdgbug();
          </script>
         </binding>
    <mod-shift>
    <binding>
            <command>property-adjust</command>
            <property>/autopilot/settings/heading-bug-deg</property>
            <step type="double">-2.5</step>
      <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
    <binding>
            <command>property-adjust</command>
            <property>instrumentation/kcs55/ki525/selected-heading-deg</property>
            <step type="double">-2.5</step>
     <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
         <binding>
            <command>nasal</command>
             <script>
              showhdgbug();
          </script>
    </binding>
    </mod-shift>
   <mod-ctrl>
    <binding>
      <command>property-adjust</command>
       <property>/autopilot/CENTURYIII/settings/roll-knob-deg</property>
       <step type="double">-0.5</step>
       <min>-30</min>
     </binding>
     <binding>
            <command>nasal</command>
             <script>
                showroll();
          </script>
     </binding>
   </mod-ctrl>
 <mod-alt>
   <binding>
            <command>property-adjust</command>
            <property>instrumentation/nav/radials/selected-deg</property>
            <step type="double">-0.25</step>
     <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
    <binding>
            <command>property-adjust</command>
            <property>instrumentation/kcs55/ki525/selected-course-deg</property>
            <step type="double">-0.25</step>
     <min>000</min>    <!-- the property will never be allowed to go below 0 -->
      <max>360</max>    <!-- ...or above 360 -->
      <wrap>true</wrap> <!-- when we hit 361, wrap back to 0 and vice-versa -->
         </binding>
     <binding>
            <command>nasal</command>
             <script>
               showcourseselect();
          </script>
     </binding>
 </mod-alt>

Summary

For convenience all of the above bindings are summarised into one file accessible at pastebin (at the time of writing identical to the combination of the snippets above but of course the pastebin might not always keep up with the Wiki - beware)