Board index FlightGear Support Hardware

Improvements to flight control subsystem

Joysticks, pedals, monitors.

Improvements to flight control subsystem

Postby XrayHotel » Thu Sep 03, 2015 8:16 am

Hello all, first time poster, long time flier. I fly around KSFO as "XH"

The story behind this little script is my Saitek X52 Pro flight stick. On windows I found the standard configuration (on FG 3.0.0) (a) non-functional, and (b) terrible even after I disentagled all the bindings. I've upgraded to 3.4 since, but use own input setup - the first thing I did was replace the defaults (are they better now?).

Anyway, since I had my old flight stick (An ancient Logitech) I thought it'd be cute to drive the 777 tiller and analogue brakes from it and map primary flight controls to the X52.

I hit a snag...

I also wanted brakes somewhere on the X52 stick (where my hand is for landing/takeoff, my other hand being on the throttle). It was easy enough to cook up some Nasal (in the joystick XML) to produce a 3 level digital brake from the X52's two stick triggers (the top has two levels, the bottom, one, for three levels of braking total) but flightgear doesn't seem to have any simple way to map TWO physical input axes to drive the same simulator control. I ended up learning some Nasal (which was refreshingly easy) and writing a little module ("aeris.nas", latin for "air") to coordinate this. Of course, I went all out and made the same trick possible for any flight control axis, so now I can (if I want) have two sets of primary flight controls on the same computer.

This would be great for teaching people to fly or pretending you're in an airbus - if you push forward on one stick and pull back on the other, nothing happens, just like a real airbus!

So, unlike the existing "controls.nas" which has a strict 1-1 mapping between physical and logical axes, with Aeris ("aeris.nas") you call one of the createAxis() functions (in some Nasal code in your Joystick's XML config) to create an axis object for the physical axis (for instance 'var elevatorAxis = aeris.flightControls.createAxis("elevator")' and in the binding add a call to "elevatorAxis.processCommand()" to update everything that needs to be updated. Aeris will automatically combine multiple physical axis inputs into a single value in a way that's appropriate for that axis (usually simple addition with a clamp.)

I think this is a better way for things to work, although it would obviously be a nightmare to replace "controls.nas" and have every aircraft just work - I have tuned my setup to work with the F-14 and 777 (the two aircraft I fly 99% of the time) - your mileage will vary with other aircraft, although basic flight controls should behave themselves.

Currently Aeris supports:
- primary flight controls (elevator, rudder, ailerons)
- analogue and multi-step digital brakes
- flaps, landing gear, parking brakes, speed brakes and arming ground spoilers, with various interlocks
- reverse thrust (although it is a bit broken)
- control inputs are attenuated (between linear and squared) across a (currently fixed) speed band to create artifical feel
- various actions will display a warning/confirmation message

Some things don't work well, in particular thrust reversers (the thumb slider on the throttle for my X52). When you try to engage it if reversers aren't installed or don't engage you'll throttle up the engines instead, highly undesirable! Reversers are a bit of a PITA, if someone can suggest the right place to look to do this the right way I'd be appreciative!

Some things that would be cool (and relatively easy) to support:
- Differential brakes (I'd probably bind this to the twist axis on my logitech)
- Separate left/right brake axes.
- Various other buttons and switches - I have a Microsoft Sidewinder gamepad I was thinking of using to control the autopilot...

The fact that I can do all this is why I love flightgear - despite it's being a giant ball of (semi ordered) spaghetti :wink:

I'm curious to know what other people think of my approach to logical/physical control axes,

Here is the code:

Firstly: "aeris.nas" - dropped into "data/Nasal"
Code: Select all
#---------------------------------------------------------------------------------------------------
# aeris.nas
# "enhanced controls"
# copyright:
#   (C) 2015 Denis Sjostrom
# contact:
#   <denis.sjostrom@gmail.com>
#---------------------------------------------------------------------------------------------------
# message(object)
# "display an in flight message"
# arguments:
#   (object) message object
#---------------------------------------------------------------------------------------------------
var message = func(m, p = 0) {
   var t = typeof(m);
   var x = "Aeris";
   if (t == "scalar") {
      x ~= ": " ~ m;   
   } else if (t == "vector") {
      foreach (var i; m) {
         x ~= ": " ~ i;
      }
   } else {
      x ~= ": " ~ t;
   }
   # comment out this line to disable messages
   setprop("/sim/screen/white", x);
}
#---------------------------------------------------------------------------------------------------
# stepProps(dst, array, delta)
# "as for controls.nas"
# notes:
#   controls.setProps() could be called here, *shrug*
#---------------------------------------------------------------------------------------------------
var stepProps = func(dst, array, delta) {
    dst = props.globals.getNode(dst);
    array = props.globals.getNode(array);
    if(dst == nil or array == nil) { return; }

    var sets = array.getChildren("setting");

    var curr = array.getNode("current-setting", 1).getValue();
    if(curr == nil) { curr = 0; }
    curr = curr + delta;
    if   (curr < 0)           { curr = 0; }
    elsif(curr >= size(sets)) { curr = size(sets) - 1; }

    array.getNode("current-setting").setIntValue(curr);
    dst.setValue(sets[curr].getValue());
}
#---------------------------------------------------------------------------------------------------
# genericCreateAxis(callback, type, options)
# "create a generic input axis object"
# arguments:
#   (callback) func(), called when axis is updated
#   (type) string, axis type
#   (options) object, axis options
# options:
#   {scaleZero} real value, scaling point for no input deflection
#   {scaleOne} real value, scaling point for maximum input deflection
# returns:
#   object, axis object
#---------------------------------------------------------------------------------------------------
var genericCreateAxis = func(callback, type, options) {
   var axis = {
      type: type,
      value: 0
   };
   if (options == nil) { options = {}; }
   axis.setValue = func(value) {
      axis.value = value;
      callback();      
   };
   axis.processCommand = func() {
      var inputValue = cmdarg().getNode("setting").getValue();
      var scaleZero = contains(options, "scaleZero") ? options.scaleZero : 0;
      var scaleOne = contains(options, "scaleZero") ? options.scaleOne : 1;
      var outputValue = (inputValue - scaleZero) / (scaleOne - scaleZero);
      axis.setValue(outputValue);
   };
   return axis;
}
#---------------------------------------------------------------------------------------------------
# createDiscreteScaler(steps, callback)
# "create a discrete scaler, converts value between zero and one to the given number of steps"
# arguments:
#   (steps) integer value, number of steps in output
#   (callback) func(step), function to recieve output value
# returns:
#   func(value), input value function
# notes:
#   this is quite a neat litle "anti dithering" algorithm, each step has a hysteresis band,
#   current state is stored inside a closure
#---------------------------------------------------------------------------------------------------
var createDiscreteScaler = func(steps, callback) {
   var currentStep = 0;
   return func(value) {
      var n = value * steps;
      var step = math.floor(n);
      if (step < 0) step = 0;
      if (step > steps - 1) step = steps - 1;
      if (step != currentStep) {
         var x = n - step;
         if ((step == 0 or x > 0.1) and (step == steps -1 or x < 0.9)) {
            currentStep = step;
            callback(step);
         }
      }
   }
};
#---------------------------------------------------------------------------------------------------
# formatPercentage(x)
# "format a percentage value"
# arguments:
#   (x) real value, 0 <= input value <= 1
# returns:
#   string, formatted value
#---------------------------------------------------------------------------------------------------
var formatPercentage = func(x) { return math.floor(x * 100) ~ "%"; }
#---------------------------------------------------------------------------------------------------
# aircraftState
# "aircraft state"
# todo:
#   expand this
#---------------------------------------------------------------------------------------------------
var aircraftState = {
   isInMotion: 0,
   isAirborne: 0
};
#---------------------------------------------------------------------------------------------------
# aircraftDesc
# "aircraft description"
# todo:
#   currently getprop() is used in a few place to query aircraft support for various things
#   whenever a given control is updated, ideally we want to do that once and store a boolean
#   here.
#---------------------------------------------------------------------------------------------------
var aircraftDesc = {
   type: getprop("/sim/aircraft")
};
#---------------------------------------------------------------------------------------------------
# engines
# "engine input interface object"
#---------------------------------------------------------------------------------------------------
var engines = (func() {
   #
   # input axes
   #
   var axes = [];
   var throttleTakeoffState = 0;
   var throttleIdleState = 0;
   #---------------------------------------------------------------------------------------------------
   # updateThrottleMessage(value)
   # "display appropriate throttle message(s)"
   # arguments:
   #   (value) real value, 0 <= throttle level <= 1
   # todo:
   #   work out a way to display reheat active/inactive messages, military power messages, etc
   #   for fighters (without too much hard coding!)
   #---------------------------------------------------------------------------------------------------
   var updateThrottleMessage = createDiscreteScaler(10, func(step) {
      if (step == 0) message("Engines Idle");
      #if (step == 9) message("Full Power");
   });
   #---------------------------------------------------------------------------------------------------
   # updateReverserMessage(value)
   # "display thrust reverser messages"
   # arguments:
   #   (value) boolean, reverser engaged? 0 = disengaged, 1 = engaged
   #---------------------------------------------------------------------------------------------------
   var updateReverserMessage = createDiscreteScaler(2, func(step) {
      message("Reverse Thrust " ~ (step ? "Engage" : "Disengage"));
   });
   #---------------------------------------------------------------------------------------------------
   # update()
   # "update engine inputs"
   #---------------------------------------------------------------------------------------------------
   var update = func() {
      
      var throttleValue = 0;
      var reverserValue = 0;

      foreach (var axis; axes) {
         if (axis.type == "throttle") {
            throttleValue = axis.value > throttleValue ? axis.value : throttleValue;
         } else if (axis.type == "reverser") {
            reverserValue = axis.value > reverserValue ? axis.value : reverserValue;
         }
      }

      throttleValue = throttleValue < 0 ? 0 : throttleValue;
      throttleValue = throttleValue > 1 ? 1 : throttleValue;

      reverserValue = reverserValue < 0 ? 0 : reverserValue;
      reverserValue = reverserValue > 1 ? 1 : reverserValue;

      updateThrottleMessage(throttleValue);
      #
      # is aircraft equipped with thrust reversers?
      # note:
      #   reversers are a pain to engage, make sure engine is at
      #   fully idle before attempting to engage
      #
      #   figure out a way to determin if thrust reversers are installed,
      #   current test doesn't work
      #
      #   reversers are a PITA! :)
      #
      if (props.globals.getNode("/controls/engines/engine/reverser") != nil) {
         var reverserEngaged = getprop("/controls/engines/engine", "reverser");
         if (reverserEngaged) {
            throttleValue = (reverserValue - 0.125) / (1 - 0.125);
            throttleValue = throttleValue < 0 ? 0 : throttleValue;
            throttleValue = throttleValue > 1 ? 1 : throttleValue;
         }
         updateReverserMessage(reverserEngaged);
         if (reverserValue > 0.125) {
            props.setAll("/controls/engines/engine", "reverser", 1);
         }
         else
         {
            props.setAll("/controls/engines/engine", "reverser", 0);
         }
      }
      props.setAll("/controls/engines/engine", "throttle", throttleValue);      

   };
   #---------------------------------------------------------------------------------------------------
   # engines.createAxis(?type?, ?options?)
   # "create an engine control input axis"
   # arguments:
   #   (type) optional string, axis type, options are "throttle"(default) or "reverser"
   #   (options) object, axis options, see genericCreateAxis()
   # returns:
   #   object, axis object
   #---------------------------------------------------------------------------------------------------
   var createAxis = func(type = "throttle", options = nil) {
      if (options == nil) {
         if (type == "throttle" or type == "reverser") {
            options = {
               scaleZero: 1,
               scaleOne: -1
            };
         }
      }
      var axis = genericCreateAxis(update, type, options);
      append(axes, axis);
      return axis;
   }
   # create the engine input object
   var engines = { createAxis: createAxis };
   return engines;
})();
#---------------------------------------------------------------------------------------------------
# gear
# "landing gear"
# notes:
#   controls landing gear, steering, brake etc
# todo:
#   add differential brake (single) axis and dual brake (two) axis support
#---------------------------------------------------------------------------------------------------
var gear = (func() {   
   var gear = {};
   var axes = [];
   # set to true if aircraft has a tiller control
   var hasTillerAxis = 0;
   #---------------------------------------------------------------------------------------------------
   # updateBrakingMessage(value)
   # "update braking message"
   # arguments:
   #   (value) real value, 0 <= value <= 1
   #---------------------------------------------------------------------------------------------------
   var lastBrakeStep = 0;
   var updateBrakingMessage = createDiscreteScaler(4, func(step) {
      if (step > lastBrakeStep and step >= 1) {
         if (aircraftState.isInMotion and !aircraftState.isAirborne) {
            message(["Braking ...", "Braking Hard ...", "Braking Very Hard ..."][step - 1]);
         }
      }
      lastBrakeStep = step;
   });
   #---------------------------------------------------------------------------------------------------
   # gear.setParkingBrake(value)
   # "apply/release the parking brake"
   # arguments:
   #   (value) integer, brake value, 1 = apply, 0 = release
   #---------------------------------------------------------------------------------------------------
   gear.setParkingBrake = func(value) {
      #var vel = getprop("/velocities/groundspeed-kt");
      var oldParkState = getprop("/controls/gear/brake-parking");
      if (value == oldParkState)
      {
         if (!aircraftState.isAirborne and value == 1 and !aircraftState.isInMotion)
            message("Aircraft is Parked");
         return;
      }
      if (aircraftState.isInMotion and value == 1) return;
      if (aircraftState.isAirborne and value == 1) return;
      message("Parking Brake " ~ ["Disenaged", "Engaged"][value]);
      setprop("/controls/gear/brake-parking", value);   
   }
   #---------------------------------------------------------------------------------------------------
   # update()
   # "update gear status"
   #---------------------------------------------------------------------------------------------------
   var update = func() {
      var tillerValue = 0;
      var brakeValue = 0;

      foreach (var axis; axes) {
         if (axis.type == "tiller") {
            tillerValue += axis.value;
         } else if (axis.type == "brake") {
            brakeValue = axis.value > brakeValue ? axis.value : brakeValue;
         }
      }

      tillerValue = tillerValue > 1 ? 1 : tillerValue;
      tillerValue = tillerValue < -1 ? -1 : tillerValue;

      brakeValue = brakeValue < 0 ? 0 : brakeValue;
      brakeValue = brakeValue > 1 ? 1 : brakeValue;

      #
      # if the user has a tiller
      #
      if (hasTillerAxis) {
          setprop("controls/gear/tiller-cmd-norm", tillerValue);          
          setprop("controls/gear/tiller-enabled", 1);
      }

      #
      # apply brake pressure
      #
        setprop("/controls/gear/brake-left", brakeValue);
        setprop("/controls/gear/brake-right", brakeValue);
      updateBrakingMessage(brakeValue);
   };
   #---------------------------------------------------------------------------------------------------
   # gear.createAxis(?type?, ?options?)
   # "create a gear input axis"
   # arguments:
   #   (type) optional string, axis type, defaults to "brake", options are "tiller", "brake"
   #   (options) object, axis options, see genericCreateAxis()
   #---------------------------------------------------------------------------------------------------
   gear.createAxis = func(type = "brake", options = nil) {
      if (options == nil) {
         if (type == "tiller") {
            options = {
               scaleZero: 0,
               scaleOne: 1
            };
         } else if (type == "brake") {
            options = {
               scaleZero: 0,
               scaleOne: 1
            };
         }
      }
      if (type == "tiller") hasTillerAxis = 1;
      var axis = genericCreateAxis(update, type, options);
      append(axes, axis);
      return axis;
   };
   #---------------------------------------------------------------------------------------------------
   # gear.createDigitalBrake(steps)
   # "create a digial brake input"
   # arguments:
   #   (steps) integer value, number of steps
   # returns:
   #   object, digital brake object
   # notes:
   #   allows you to bind two (or more) buttons to work together as a multi-step brake.
   #   my Saitek controller has a multi step trigger that behaves as two buttons, when the
   #   trigger is fully pressed both are down. Call add(+1) and add(-1) in the button down
   #   and button up handlers respectively.
   #---------------------------------------------------------------------------------------------------
   gear.createDigitalBrake = func(steps) {
      var brake = {};
      var currentBrakeInput = 0;
      var currentBrakeOutput = 0;
      var axis = gear.createAxis("brake");
      var update = func() {
         var x = currentBrakeInput / steps;
         axis.setValue(x);
      }
      brake.add = func(value) {
         currentBrakeInput = currentBrakeInput + value;
         if (currentBrakeInput < 0) currentBrakeInput = 0;
         if (currentBrakeInput > steps) currentBrakeInput = steps;
         update();
      }
      return brake;
   };
   #---------------------------------------------------------------------------------------------------
   # setGearState(value)
   # "raise/lower the landing gear"
   # arguments:
   #   (value) integer value, new gear lever stae, 0 = up, 1 = down
   #---------------------------------------------------------------------------------------------------
    var setGearState = func(value)
    {
        var oldGear = getprop("/controls/gear/gear-down");
        controls.gearDown(value);
        var newGear = getprop("/controls/gear/gear-down");
        if (newGear != oldGear)
        {
         if (newGear == 0) message("Gear Up");
         if (newGear == 1) message("Gear Down");
        }
    }
   #---------------------------------------------------------------------------------------------------
   # gear.raise()
   # "raise the landing gear"
   #---------------------------------------------------------------------------------------------------
   gear.raise = func() { setGearState(-1); };
   #---------------------------------------------------------------------------------------------------
   # gear.lower()
   # "lower the landing gear"
   #---------------------------------------------------------------------------------------------------
   gear.lower = func() { setGearState(1); };
   #---------------------------------------------------------------------------------------------------
   return gear;
})();
#---------------------------------------------------------------------------------------------------
# flightControl
# "primary flight controls"
# notes:
#   allows multiple controllers to be bound to the same axis. Great for pilot/copilot setups!
#---------------------------------------------------------------------------------------------------
var flightControl = (func() {
   var flightControl = {};
   var axes = [];
   #---------------------------------------------------------------------------------------------------
   # clampValue(v)
   # "clamp a normalized input value"
   # arguments:
   #   (v) real value, input value
   # returns:
   #   real value, -1 <= v <= 1
   #---------------------------------------------------------------------------------------------------
   var clampValue = func(v) {
      if (v < -1) return -1;
      if (v > 1) return 1;
      return v;
   }
   #---------------------------------------------------------------------------------------------------
   # attenuateControl(inputValue)
   # "apply input attenuation"
   # arguments:
   #   (inputValue) real value, control input value
   # returns:
   #   real value, attenuated control value
   # notes:
   #   applies increasing "control resistance" as airspeed increases across a band from 150 to
   #   400 knots, which helps to improve the feel of the controls. Full control authority is
   #   still available at any airspeed, but control response is increasingly 'curved' to increse
   #   the required stick pressure for a given control deflection.
   # todo:
   #   compute the attenuation speed band automatically
   #---------------------------------------------------------------------------------------------------
   var attenuateControl = func(inputValue) {
      var min = 150;
      var max = 400;
      var vel = getprop("/velocities/airspeed-kt");
      var resist = (vel - min) / (max - min);
      if (resist < 0) resist = 0;
      if (resist > 1) resist = 1;
      var valueSquared = inputValue * inputValue;
      if (inputValue < 0) valueSquared = -valueSquared;
      return inputValue + (valueSquared - inputValue) * resist;   
   }
   #---------------------------------------------------------------------------------------------------
   # flightControl.update()
   # "update primary flight controls"
   #---------------------------------------------------------------------------------------------------
   var update = func() {
      var aileronValue = 0;
      var elevatorvalue = 0;
      var rudderValue = 0;
      foreach (var axis; axes) {
         if (axis.type == "aileron") {
            aileronValue += axis.value;
         } else if (axis.type == "elevator") {
            elevatorvalue += axis.value;
         } else if (axis.type == "rudder") {
            rudderValue += axis.value;
         }
      }

      aileronValue = clampValue(aileronValue);
      elevatorvalue = clampValue(elevatorvalue);
      rudderValue = clampValue(rudderValue);

      setprop("/controls/flight/aileron", attenuateControl(aileronValue));
      setprop("/controls/flight/elevator", attenuateControl(elevatorvalue));
      setprop("/controls/flight/rudder", attenuateControl(rudderValue));
   };
   flightControl.update = update;
   #---------------------------------------------------------------------------------------------------
   # flightControl.createAxis(?type?, ?options?)
   # "create a primary flight control axis"
   # arguments:
   #   (type) optional string, axis type, options are "aileron" (default), "rudder" and "elevator"
   #   (options) optional object, axis options, see genericCreateAxis()
   # returns:
   #   object, axis object
   #---------------------------------------------------------------------------------------------------
   flightControl.createAxis = func(type = "aileron", options = nil) {
      var axis = genericCreateAxis(update, type, options);
      append(axes, axis);
      return axis;
   };
   #---------------------------------------------------------------------------------------------------
   return flightControl;
})();
#---------------------------------------------------------------------------------------------------
# spoilers/speed brake
#---------------------------------------------------------------------------------------------------
var spoilers = (func() {
   var spoilers = {};
   #---------------------------------------------------------------------------------------------------
   var extendState = 0;
   var armState = 0;
   var latchState = 0;
   #---------------------------------------------------------------------------------------------------
   # spoilers.getExtendState()
   # "query the extension state of the spoilers"
   # returns:
   #   boolean, 0 = retracted, 1 = extended
   #---------------------------------------------------------------------------------------------------
   spoilers.getExtendState = func() { return extendState; }
   #---------------------------------------------------------------------------------------------------
   # spoilers.getArmState()
   # "query the arm state of the spoilers"
   # returns:
   #   boolean, 0 = disarmed, 1 = armed
   #---------------------------------------------------------------------------------------------------
   spoilers.getArmState = func() { return armState; }
   #---------------------------------------------------------------------------------------------------
   # spoilers.getLatchState()
   # "query the latch state of the spoilers"
   # returns:
   #   boolean, 0 = unlatched, 1 = latched
   #---------------------------------------------------------------------------------------------------
   spoilers.getLatchState = func() { return latchState; }
   #---------------------------------------------------------------------------------------------------
   # update()
   # "update spoiler configuration"
   #---------------------------------------------------------------------------------------------------
   var update = func()
   {
      #
      # F-14
      #
      setprop("/controls/flight/ground-spoilers-armed", armState);
      setprop("/controls/flight/speedbrake", extendState);
      #
      # airliners (777, etc should work)
      #
      if (extendState != 0)
      {
         setprop("/controls/flight/speedbrake-lever", 1 + extendState);
      } else {
         setprop("/controls/flight/speedbrake-lever", armState);
      }
   }
   #---------------------------------------------------------------------------------------------------
   # spoilers.arm(value)
   # "arm/disarm the spoilers"
   # arguments:
   #   (value) boolean, new arm state, 0 = disarmed, 1 = armed
   # notes:
   #   the spoilers cannot be armed or disarmed while the speed brake is deployed
   #---------------------------------------------------------------------------------------------------
    spoilers.arm = func(value)
    {
      if (extendState) {
         if (armState == 0 and value == 1)
            message("Warning, Speed Brake is Extended, Spoilers Cannot be Armed");
      }      
      else if (value != armState)
      {
         armState = value;
         if (armState == 1) message("Spoilers Armed");
         if (armState == 0) message("Spoilers Disarmed");
         update();
        }
    }
   #---------------------------------------------------------------------------------------------------
   # spoilers.latch(value)
   # "latch/unlatch the spoilers"
   # arguments:
   #   (value) boolean, new latch state, 0 = unlatched (retracted), 1 = latched (extended)
   #---------------------------------------------------------------------------------------------------
   spoilers.latch = func(value) {
      if (armState)
      {
         message("Warning, Spoilers Disarmed");
         armState = 0;
      }
      if (value == 0) {
         if (latchState == 1) {
            message("Speed Brake Unlatched");
         }
         extendState = 0;
         latchState = 0;
      } else {
         if (latchState == 0) {
            message("Speed Brake Latched");
         }
         extendState = 1;
         latchState = 1;
      }
        update();            
   }
   #---------------------------------------------------------------------------------------------------
   # spoilers.extend(value)
   # "extend/retract the spoilers/speed brake"
   # arguments:
   #   (value) boolean, 0 = retact, 1 = extend
   #---------------------------------------------------------------------------------------------------
    spoilers.extend = func(value)
    {
        if (latchState and value == 0)
        {
         message("Speed Brake Unlatched");
         latchState = 0;
        }
        if (armState)
        {
         message("Warning, Spoilers Disarmed");
         armState = 0;
        }
        extendState = value;
        update();
    }
   #---------------------------------------------------------------------------------------------------
   return spoilers;
})();
#---------------------------------------------------------------------------------------------------
# flaps
# todo:
#   add axis support - for crazy people who have an axis to spare
#---------------------------------------------------------------------------------------------------
var flaps = (func() {
   var flaps = {};
   #---------------------------------------------------------------------------------------------------
   # adjust(amount)
   # "adjust flaps setting"
   # arguments:
   #   (amount) integer value, number of steps to adjust (signed)
   # notes:
   #   whoever wrote the F-14 flaps code needs to be shot ...
   #   AFAICT it's replacing the pertient functions in controls.nas o_o
   #---------------------------------------------------------------------------------------------------
   var adjust = func(amount) {
      if (props.globals.getNode("/controls/flight/flapscommand") != nil) {
         #
         # F-14 special case hack (yuck)
         #
         var oldFlaps = getprop("/controls/flight/flapscommand");
         var val = amount + getprop("/controls/flight/flapscommand");
         setprop("/controls/flight/flapscommand", val > 1 ? 1 : val < 0 ? 0 : val);
         var newFlaps = getprop("/controls/flight/flapscommand");
         if (newFlaps != oldFlaps)
            message((newFlaps ? "Extend" : "Retract") ~ " Flaps");
      }
      else
      {
         #
         # normal (vaguely sane) flaps code
         #
         if(props.globals.getNode("/sim/flaps") != nil) {
            #
            # the Right Thing
            # step through flaps settings
            #
            var oldFlaps = getprop("/controls/flight/flaps");
            stepProps("/controls/flight/flaps", "/sim/flaps", amount);         
            var newFlaps = getprop("/controls/flight/flaps");
            if (newFlaps != oldFlaps)
            {
               if (newFlaps == 0) {
                  message("Retract Flaps");
               }
               else if (newFlaps == 1) {
                  message("Full Flaps");
               }
               else
               {
                  message("Flaps " ~ formatPercentage(newFlaps));
               }
            }
         }
         else
         {
            #
            # simple flaps up/down
            #
            var oldFlaps = getprop("/controls/flight/flaps");
            var val = amount + getprop("/controls/flight/flaps");
            setprop("/controls/flight/flaps", val > 1 ? 1 : val < 0 ? 0 : val);
            var newFlaps = getprop("/controls/flight/flaps");
            if (newFlaps != oldFlaps)
            {
               message("Flaps " ~ (newFlaps ? "Extend" : "Retract"));
            }      
         }
   
      }
   }
   #---------------------------------------------------------------------------------------------------
   # flaps.lower(?amount?)
   # "lower flaps"
   # arguments:
   #   (amount) optional integer value, number of steps, defaults to 1
   #---------------------------------------------------------------------------------------------------
   flaps.lower = func(amount = 1) {
      adjust(amount);
   }
   #---------------------------------------------------------------------------------------------------
   # flaps.raise(?amount?)
   # "raise flaps"
   # arguments:
   #   (amount) optional integer value, number of steps, defaults to 1
   #---------------------------------------------------------------------------------------------------
   flaps.raise = func(amount = 1) {
      adjust(-amount);
   }
   #---------------------------------------------------------------------------------------------------
   return flaps;
})();
#---------------------------------------------------------------------------------------------------
# "main aeris update function"
# notes:
#   this is probably wrong, but we need inputs to change as airspeed changes
#   see: attenuateControl() in aeris.flightControl
#   at any rate, it's only done 8 times a second
#---------------------------------------------------------------------------------------------------
settimer(func() {
   var updateBegin = 0;
   var aicraftMotionState = 0;
   var aircraftAirborneState = 0;
   var update = func() {
      # update loop begin?
      if (!updateBegin) {
         message(aircraftDesc.type);
         updateBegin = 1;
      }
      #
      var aglFeet = getprop("/position/gear-agl-ft");
      var groundSpeedKnots = getprop("/velocities/groundspeed-kt");
      
      #
      # is aircraft airborne?
      #
      if (aglFeet > 2) {
         # if aircraft is airborne, it is by definition "in motion"
         aicraftMotionState = 1;
         if (aircraftAirborneState == 0) {
            message("Airborne");
            aircraftAirborneState = 1;
         }
      } else if (aglFeet < 1) {
         if (aircraftAirborneState == 1) {
            message("Wheel Contact");
            aircraftAirborneState = 0;
         }         
      }

      #
      # is aircraft in motion?
      #
      if (groundSpeedKnots > 1 or aircraftAirborneState == 1) {
         if (aicraftMotionState == 0) {
            message("Aicraft is in Motion");
            aicraftMotionState = 1;
         }
      } else if (groundSpeedKnots < 0.1 and aircraftAirborneState == 0) {
         if (aicraftMotionState == 1) {
            message("Aircraft has stopped");
            aicraftMotionState = 0;
         }
      }

      #
      # update aircraft state
      #
      aircraftState.isInMotion = aicraftMotionState;
      aircraftState.isAirborne = aircraftAirborneState;

      flightControl.update();
   }

   var timer = maketimer(0.125, update);
   timer.start();
}, 1);


The XML for the X52 Pro - "X52-pro.xml"
Code: Select all
<?xml version="1.0"?>
<!--
  Based on X52.xml and Aviator.xml
  Modified by Arvid Norlander; 2007-12-03
  Modified by Keiran Smith <affix@affix.me>; 2012-10-10
 
  Extensively modified by Denis Sjostrom <denis.sjostrom@gmail.com>, 2015
  (actually, more or less rewritten)
  Requires Aeris control module
 
  This file is released under the GPL license version 2 or later. 
-->
<PropertyList>

   <name>Saitek X52 Pro Flight Control System</name>
   <name>Saitek X52 Pro Flight Controller</name>
   <name>Saitek Saitek X52 Pro Flight Control System</name>

  <nasal>
      <script>
         <![CDATA[       
        #---------------------------------------------------------------------------------------------------
        # message(msg)
        # "simple debug/control status message function"
        # arguments:
        #   (msg) string, message to display
        # notes:
        #   shunts the message through to aeris' message function
        #   no longer used
        #---------------------------------------------------------------------------------------------------
        var message = func(msg) { aeris.message(msg); }
        #---------------------------------------------------------------------------------------------------
        # setMode(nMode)
        # "update mode (mode wheel on stick)
        # arguments:
        #   (nMode) integer value, new mode
        # notes:
        #   can't get the mode wheel to work, stupid saitek droolware drivers
        #---------------------------------------------------------------------------------------------------
        var setMode = func(nMode) {
          message("Mode " ~ nMode);
        }
        #---------------------------------------------------------------------------------------------------
        # trackBrakePressure(amount)
        # "add (or remove) from the total amount of currently applied braking pressure"
        # arguments:
        #   (amount) integer value, number of brake steps to adjust, signed
        #---------------------------------------------------------------------------------------------------
        var brakeTotal = 0;
        var trackBrakePressure = func(amount) {
          digitalBrake.add(amount);
          var lastBrakeTotal = brakeTotal;
          brakeTotal += amount;
          if (brakeTotal and shiftState)
          {
            aeris.gear.setParkingBrake(1);
            shiftState = 0;
          }
        }
        #---------------------------------------------------------------------------------------------------
        # setShift(value)
        # "set shift state"
        # arguments:
        #   (value) integer, new shift state, 0 = release, 1 = press
        #---------------------------------------------------------------------------------------------------
        var shiftState = 0;
        var setShift = func(value)
        {
          if (value != shiftState)
          {
            if (value == 1)
            {
              if (brakeTotal)
              {
                aeris.gear.setParkingBrake(1);
              } else {
                shiftState = 1;
              }
            }
            else if (value == 0)
            {
              # shift released
              shiftState = 0;
              if (aeris.spoilers.getLatchState()) {
                aeris.spoilers.latch(0);
              } else if (!brakeTotal) {
                aeris.gear.setParkingBrake(0);
              }
             
            }
          }
        }       
        #---------------------------------------------------------------------------------------------------
        # throttleAxis
        # "main throttle axis"
        #---------------------------------------------------------------------------------------------------
        var throttleAxis = aeris.engines.createAxis(
          "throttle",
          {
            scaleZero: 1,
            scaleOne: -1
          }
        );
        #---------------------------------------------------------------------------------------------------
        # reverserAxis
        # "thrust reverser, Thumb slider"
        #---------------------------------------------------------------------------------------------------
        var reverserAxis = aeris.engines.createAxis(
          "reverser",
          {
            scaleZero: 1,
            scaleOne: -1
          }
        );       
        #---------------------------------------------------------------------------------------------------
        # digitalBrake
        # "digital brake input"
        # notes:
        #   a 3 level digital brake input, controlled by both stick triggers
        #   top trigger has two levels
        #   bottom trigger has one
        #   squeeze both for maximum braking!
        #---------------------------------------------------------------------------------------------------
        var digitalBrake = aeris.gear.createDigitalBrake(3);
        #---------------------------------------------------------------------------------------------------
        # aileronAxis, rudderAxis, elevatorAxis
        # "primary flight control axes"
        #---------------------------------------------------------------------------------------------------
        var aileronAxis = aeris.flightControl.createAxis("aileron");
        var elevatorAxis = aeris.flightControl.createAxis(
          "elevator",
          {
            scaleOne: -1,
            scaleZero: 0
          }
        );
        var rudderAxis = aeris.flightControl.createAxis("rudder");             

        var extendSpoilers = func(value) {
          if (value == 0 and shiftState) {
            aeris.spoilers.latch(1);
            shiftState = 0;
          } else {
            aeris.spoilers.extend(value);
          }
        }
       
        var armSpoilers = func(value) {
          aeris.spoilers.arm(value);
        }
      ]]>
      </script>
   </nasal>

  <!-- 
    Primary flight controls 
  --> 
  <axis>
    <number>
      <mac>0</mac>
      <unix>0</unix>
      <windows>0</windows>
    </number>
    <desc>Aileron</desc>
    <binding>
      <command>nasal</command>
      <script>
        aileronAxis.processCommand();
      </script>
    </binding>
  </axis>
 
  <axis>
    <number>
      <mac>1</mac>
      <unix>1</unix>
      <windows>1</windows>
    </number>
    <desc>Elevator</desc>
    <binding>
      <command>nasal</command>
      <script>
        elevatorAxis.processCommand();
      </script>
    </binding>
  </axis>
 
  <axis>
    <number>
      <mac>3</mac>
      <unix>3</unix>
      <windows>3</windows>
    </number>
    <desc>Rudder</desc>
    <binding>
      <command>nasal</command>
      <script>
        rudderAxis.processCommand();
      </script>
    </binding>
  </axis>
 
  <!--
    Bottom stick hat
  -->
 
   <axis>
      <number>
         <mac>6</mac>
         <unix>6</unix>
         <windows>6</windows>
      </number>
      <desc>View Direction</desc>
      <low>
         <repeatable>true</repeatable>
         <binding>
            <command>nasal</command>
            <script>
             view.panViewDir(1);
            </script>
         </binding>
      </low>
      <high>
         <repeatable>true</repeatable>
         <binding>
            <command>nasal</command>
            <script>
             view.panViewDir(-1);
            </script>
         </binding>
      </high>
   </axis>

   <axis>
      <number>
         <mac>7</mac>
         <unix>7</unix>
         <windows>7</windows>
      </number>
      <desc>View Elevation</desc>
      <low>
         <repeatable>true</repeatable>
         <binding>
            <command>nasal</command>
            <script>
              view.panViewPitch(-1);
            </script>
         </binding>
      </low>
      <high>
         <repeatable>true</repeatable>
         <binding>
            <command>nasal</command>
            <script>
              view.panViewPitch(1);
            </script>
         </binding>
      </high>
   </axis>
-
  <!--
    Throttle
  --> 
   <axis>
      <number>
         <mac>2</mac>
         <unix>2</unix>
         <windows>2</windows>
      </number>
      <desc>Throttle</desc>
      <binding>
         <command>nasal</command>
         <script>
        throttleAxis.processCommand();
      </script>
      </binding>
   </axis>

 
  <axis>
    <number>
      <mac>4</mac>
      <unix>4</unix>
      <windows>4</windows>
    </number>
    <desc>Reverser</desc>
    <binding>
      <command>nasal</command>
      <script>
        reverserAxis.processCommand();
      </script>
    </binding>
  </axis>
 
   <!-- Brake, Trigger -->
   <button n="0">
      <desc>Brakes</desc>
      <binding>
         <command>nasal</command>
         <script>
        trackBrakePressure(1);
      </script>
      </binding>
      <mod-up>
         <binding>
            <command>nasal</command>
            <script>
          trackBrakePressure(-1);
        </script>
         </binding>
      </mod-up>
   </button>
  <!-- Brake, Moar Trigger! -->
  <button n="14">
    <desc>Moar Brakes</desc>
    <binding>
      <command>nasal</command>
      <script>
        trackBrakePressure(1);
      </script>
    </binding>
    <mod-up>
      <binding>
        <command>nasal</command>
        <script>
          trackBrakePressure(-1);
        </script>
      </binding>
    </mod-up>
  </button>
  <!--
    Lower trigger - yet moar braking!
  -->
  <button n="5">
    <desc>Moar Brakes</desc>
    <binding>
      <command>nasal</command>
      <script>
        trackBrakePressure(1);
      </script>
    </binding>
    <mod-up>
      <binding>
        <command>nasal</command>
        <script>
          trackBrakePressure(-1);
        </script>
      </binding>
    </mod-up>
  </button>


  <!--
    Button C - Shift modifier
    (thumb button on the stick just below the top hat)
  -->
  <button n="4">
    <desc>Shift switch for X52</desc>
    <binding>
      <command>nasal</command>
      <script>
        setShift(1);
      </script>
    </binding>
    <mod-up>
      <binding>
        <command>nasal</command>
        <script>
          setShift(0);
        </script>
      </binding>
    </mod-up>
  </button>
 
   <!--
    Button D (throttle thumb button)
    speed brakes (hold to slow down!)
    shift (button C) + button D to latch in extended position
    shift or button D to release
  -->
  <button n="6">
    <desc>Spoilers</desc>
    <repeatable type="bool">false</repeatable>
    <binding>
      <command>nasal</command>
      <script>
        extendSpoilers(1);
      </script>
    </binding>
    <mod-up>
      <binding>
        <command>nasal</command>
        <script>
          extendSpoilers(0);
        </script>
      </binding>
    </mod-up>
  </button>

 
  <!--
    lefthand rocker switch, raise/lower flaps
  -->
  <button n="8">
    <desc>Flaps Up</desc>
    <binding>
      <command>nasal</command>
      <script>aeris.flaps.raise();</script>
    </binding>
  </button>
  <button n="9">
      <desc>Flaps Down</desc>
      <binding>
         <command>nasal</command>
         <script>aeris.flaps.lower();</script>
      </binding>
   </button>

  <!--
    middle rocker switch, arm/disarm spoilers
  -->
  <button n="10">
    <desc>Disarm Ground Spoilers</desc>
    <binding>
      <command>nasal</command>
      <script>
          armSpoilers(0);
      </script>
    </binding>
  </button> 
  <button n="11">
    <desc>Arm Ground Spoilers</desc>
    <binding>
      <command>nasal</command>
      <script>
          armSpoilers(1);
      </script>
    </binding>
  </button>
 

  <!--
    Raise/Lower landing gear
    Button 10
    Leftmost switch
  -->
  <button n="12">
    <desc>Raise Landing Gear</desc>
    <binding>
      <command>nasal</command>
      <script>
        aeris.gear.raise();
      </script>
    </binding>
  </button>
  <button n="13">
    <desc>Lower Landing Gear</desc>
    <binding>
      <command>nasal</command>
      <script>
        aeris.gear.lower();
      </script>
    </binding>
  </button>
 
  <!--
    Upper stick hat - rudder and elevator trim
    todo:
      write a combining multi axis trim module for aeris     
  -->
  <button n="21">
    <desc>Elevator trim up</desc>
    <repeatable type="bool">true</repeatable>
    <binding>
      <command>nasal</command>
      <script>
        controls.elevatorTrim(-1);
      </script>
    </binding>
  </button>
 
  <button n="19">
      <desc>Elevator trim down</desc>
      <repeatable type="bool">true</repeatable>
      <binding>
         <command>nasal</command>
         <script>
           controls.elevatorTrim(1);
         </script>
      </binding>
   </button>

   <button n="22">
      <desc>Rudder trim left</desc>
      <repeatable type="bool">true</repeatable>
      <binding>
         <command>nasal</command>
         <script>
            controls.rudderTrim(-1);
         </script>
      </binding>
   </button>

  <button n="20">
    <desc>Rudder trim right</desc>
    <repeatable type="bool">true</repeatable>
    <binding>
      <command>nasal</command>
      <script>
        controls.rudderTrim(1);
      </script>
    </binding>
  </button>

  <!--
    Does not work, infuriatingly so ...
    Can't see anything anywhere to suggest that flightgear is getting sent this information
    on windows 8.1, although the Windows joystick control panel can see it...   
    Idiot Saitek drivers are typical fucking droolware.
    (can't get the 'I' button to work either, FFS!!)
  -->
  <button n="27">
      <desc>Mode 1</desc>
      <binding>
         <command>nasal</command>
         <script>setMode(1);</script>
      </binding>
   </button>

   <button n="28">
      <desc>Mode 2</desc>
      <binding>
         <command>nasal</command>
         <script>setMode(2);</script>
      </binding>
   </button>

   <button n="29">
      <desc>Mode 3</desc>
      <binding>
         <command>nasal</command>
         <script>setMode(3);</script>
      </binding>
   </button>
 
</PropertyList>


An finally, the XML for my Logitech Extreme 3D (input device manufacturers just love MAXTREME things don't they? :wink: )

Code: Select all
<?xml version="1.0" ?>
<PropertyList>
  <name>Logitech Extreme 3D</name>
  <!--
    Use stick for tiller and brake
  --> 
  <nasal>
    <script>
      var tillerAxis = aeris.gear.createAxis("tiller");
      var brakeAxis = aeris.gear.createAxis("brake");
    </script>
  </nasal>
  <axis>
    <number>
      <mac>0</mac>
      <unix>0</unix>
      <windows>0</windows>
    </number>   
    <dead-band type="double">0.1</dead-band>"
    <desc>Tiller</desc>
    <binding>
      <command>nasal</command>
      <script>tillerAxis.processCommand();</script>
    </binding>
  </axis>
  <axis>
    <number>
      <mac>1</mac>
      <unix>1</unix>
      <windows>1</windows>
    </number>
    <desc>Brake</desc>
    <binding>
      <command>nasal</command>
      <script>
        brakeAxis.processCommand();
      </script>
    </binding>
  </axis>
 


  <!--
    use stick for primary flight controls
  -->
  <!--
  <nasal>
    <script>
      var elevatorAxis = aeris.flightControl.createAxis(
      "elevator",
      {
      scaleOne: -1,
      scaleZero: 0
      }
      );
      var rudderAxis = aeris.flightControl.createAxis("rudder");
      var aileronAxis = aeris.flightControl.createAxis("aileron");
    </script>
  </nasal>
  <axis>
    <dead-band type="double">0.1</dead-band>"
    <number>
      <mac>0</mac>
      <unix>0</unix>
      <windows>0</windows>
    </number>
    <desc>Aileron</desc>
    <binding>
      <command>nasal</command>
      <script>
        aileronAxis.processCommand();
      </script>
    </binding>
  </axis>
  <axis>
    <dead-band type="double">0.1</dead-band>"
    <number>
      <mac>1</mac>
      <unix>1</unix>
      <windows>1</windows>
    </number>
    <desc>Elevator</desc>
    <binding>
      <command>nasal</command>
      <script>
        elevatorAxis.processCommand();
      </script>
    </binding>
  </axis>
  <axis>
    <dead-band type="double">0.1</dead-band>"
    <number>
      <mac>3</mac>
      <unix>3</unix>
      <windows>3</windows>
    </number>
    <desc>Rudder</desc>
    <binding>
      <command>nasal</command>
      <script>
        rudderAxis.processCommand();
      </script>
    </binding>
  </axis>
  -->
 
  <!--
    view direction (hat) 
  -->
  <axis>
    <desc>View Direction</desc>
    <number>
      <unix>6</unix>
      <windows>6</windows>
      <mac>6</mac>
    </number>
    <low>
      <repeatable>true</repeatable>
      <binding>
        <command>property-adjust</command>
        <property>/sim/current-view/goal-heading-offset-deg</property>
        <step type="double">2.0</step>
      </binding>
    </low>
    <high>
      <repeatable>true</repeatable>
      <binding>
        <command>property-adjust</command>
        <property>/sim/current-view/goal-heading-offset-deg</property>
        <step type="double">-2.0</step>
      </binding>
    </high>
  </axis>
  <axis>
    <desc>View Elevation</desc>
    <number>
      <unix>7</unix>
      <windows>7</windows>
      <mac>7</mac>
    </number>
    <low>
      <repeatable>true</repeatable>
      <binding>
        <command>property-adjust</command>
        <property>/sim/current-view/goal-pitch-offset-deg</property>
        <step type="double">-2.0</step>
      </binding>
    </low>
    <high>
      <repeatable>true</repeatable>
      <binding>
        <command>property-adjust</command>
        <property>/sim/current-view/goal-pitch-offset-deg</property>
        <step type="double">2.0</step>
      </binding>
    </high>
  </axis>
</PropertyList>


Thanks all.

*PS this is my first Nasal script, so It's sub optimal. Be gentle. :D
XrayHotel
 
Posts: 31
Joined: Thu Sep 03, 2015 6:33 am
Callsign: XH

Re: Improvements to flight control subsystem

Postby XrayHotel » Thu Sep 03, 2015 8:20 am

Oh, I should mention - the bindings are set up for Windows - If OSX or Linux doesn't use the same numbers, you'll need to update the XML...
XH.
* Remember, keep the blue side up and avoid the cumulo-granitus!
XrayHotel
 
Posts: 31
Joined: Thu Sep 03, 2015 6:33 am
Callsign: XH

Re: Improvements to flight control subsystem

Postby sim » Mon Sep 07, 2015 4:12 pm

Some things don't work well, in particular thrust reversers (the thumb slider on the throttle for my X52). When you try to engage it if reversers aren't installed or don't engage you'll throttle up the engines instead, highly undesirable! Reversers are a bit of a PITA, if someone can suggest the right place to look to do this the right way I'd be appreciative!


Why waste your slider XH? Reverser script on a button should work fine. You could use a toggle script so one push is Reversers ON and next push of button puts 'em OFF.
If you'd rather not toggle, use normal reverser script to fire 'em up then use <mod-up> so reversers only activate for the time button is pressed and cease operating once you release button.
:wink:

Something like this using <mod-up>
Code: Select all
<button n="5"><!--OR YOUR CHOSEN BUTTON No-->
<desc>ReverseThrust</desc>
 <repeatable type="bool">false</repeatable>
 <binding>
 <command>nasal</command>
<script>
props.setAll("/controls/engines/engine", "reverser", 1);
props.setAll("/controls/engines/engine", "throttle", 0);
setprop("/controls/flight/speedbrake", 1);
</script> </binding>
<mod-up>
<binding>
 <command>nasal</command>
 <script>
props.setAll("/controls/engines/engine", "reverser", 0);
setprop("/controls/flight/speedbrake", 0);</script>
 </binding>  </mod-up>
</button>
User avatar
sim
 
Posts: 1431
Joined: Tue Jun 30, 2009 3:13 pm
Location: Shropshire England
Callsign: Fly4Fun
Version: 0.9.10 up
OS: 64 Win 10 HD6450

Re: Improvements to flight control subsystem

Postby XrayHotel » Sat Sep 12, 2015 6:07 pm

I picked the slider for two reasons. One, the X52 has a surplus of axes, and the thumb slider was the natural place to put reverse thrust. The idea is to match the behaviour of the reversers on a typical throttle quadrant.

Secondly, it gives me analog control, slide it halfway back for reduced reverse thrust. I am under the impression that reverse thrust in transport aircraft is not an either or affair - pull the thrust reverse levers back halfway, get half the reverse thrust?
XH.
* Remember, keep the blue side up and avoid the cumulo-granitus!
XrayHotel
 
Posts: 31
Joined: Thu Sep 03, 2015 6:33 am
Callsign: XH

Re: Improvements to flight control subsystem

Postby sim » Sun Sep 13, 2015 3:25 pm

@XH, Variable Reverse Thrust may be possible although I'm not sure FG presently supports it.

You could use <high> or <low> points on your slider axis to incorporate something like the reverser script I gave in my earlier post.
That simply uses one end of your axis as though it were a button so only gives reversers ON or OFF.

Coding a suitable axis script using "slewprop" maybe possible but I'd always assumed Reversers in FG were either fully ON or else OFF.
PushBack scripts may be worth exploring since they reverse engine thrust too. :wink: :

Try
<script>props.setAll("/controls/engines/engine", "reverser", 0.5);</script>
to see if that gives half reverser thrust? If that works replace the 0.5 with a slewprop code nominating your slider.
Sort of thing Macnab or Phillosopher can advise you on.
User avatar
sim
 
Posts: 1431
Joined: Tue Jun 30, 2009 3:13 pm
Location: Shropshire England
Callsign: Fly4Fun
Version: 0.9.10 up
OS: 64 Win 10 HD6450

Re: Improvements to flight control subsystem

Postby XrayHotel » Tue Sep 15, 2015 4:14 pm

I'm all rather new to Nasal script - I just poked at the property tree randomly until things worked (sort of.) After engaging reversers:

Code: Select all
props.setAll("/controls/engines/engine", "reverser", 1);


... like so, I found you don't actually get any reverse thrust unless you spool the engines up as well, so I have things set up to do so, proportional to the position of the thrust reverse slider.

Unfortunately, I haven't figured out a way to determine if reversers are (a) installed and (b) if they actually engaged, which is unfortunate, the result being the engines spooling up and increasing thrust if the reversers don't engage (which happens if the engines aren't completely at idle) which is pretty bad during landing roll out.

No idea what the "right" way to do things is, but judging by the variation between aircraft, there isn't a "right" way for many, many things ;)
XH.
* Remember, keep the blue side up and avoid the cumulo-granitus!
XrayHotel
 
Posts: 31
Joined: Thu Sep 03, 2015 6:33 am
Callsign: XH

Re: Improvements to flight control subsystem

Postby Richard » Tue Sep 15, 2015 8:42 pm

XrayHotel wrote in Thu Sep 03, 2015 8:16 am: # whoever wrote the F-14 flaps code needs to be shot ...
# AFAICT it's replacing the pertient functions in controls.nas o_o


Ironically this was originally to support the joystick flaps command. (see f-14b/Nasal/flaps_computer.,nas). I suspect the standard behaviour has developed since this code was written and most certainly it was for a good reason at the time. Bear in mind that the F-14 flaps / slats are deployed by the CADC at high AOA (low speed).

If it needs fixing then I'm happy to understand how you think it should work then I can look into changing it; or you can experiment yourself and submit a fix (via a pull request to my development repository).

--Richard
Richard
 
Posts: 810
Joined: Sun Nov 02, 2014 11:17 pm
Version: Git
OS: Win10

Re: Improvements to flight control subsystem

Postby MIG29pilot » Tue Sep 15, 2015 8:58 pm

Richard wrote in Tue Sep 15, 2015 8:42 pm:
XrayHotel wrote in Thu Sep 03, 2015 8:16 am: # whoever wrote the F-14 flaps code needs to be shot ...



If it needs fixing then I'm happy to understand how you think it should work then I can look into changing it; or you can experiment yourself and submit a fix (via a pull request to my development repository).

--Richard

*suppressed giggle and chuckle in the background*
User avatar
MIG29pilot
 
Posts: 1465
Joined: Tue May 19, 2015 5:03 pm
Location: 6 feet under Snow
Callsign: MIG29pilot
Version: 2020.1.3
OS: Windows 10

Re: Improvements to flight control subsystem

Postby Johan G » Wed Sep 16, 2015 3:13 pm

I would say that is a good way of handling something said like that. :)
Low-level flying — It's all fun and games till someone looses an engine. (Paraphrased from a YouTube video)
Improving the Dassault Mirage F1 (Wiki, Forum, GitLab. Work in slow progress)
Some YouTube videos
Johan G
Moderator
 
Posts: 6634
Joined: Fri Aug 06, 2010 6:33 pm
Location: Sweden
Callsign: SE-JG
IRC name: Johan_G
Version: 2020.3.4
OS: Windows 10, 64 bit

Re: Improvements to flight control subsystem

Postby XrayHotel » Wed Sep 16, 2015 9:27 pm

Code: Select all
# whoever wrote the F-14 flaps code needs to be shot ...
# AFAICT it's replacing the pertient functions in controls.nas o_o

How did that comment get in there? Let me rephrase that "I probably wouldn't do it that way." Ahem... :mrgreen:

Actually the need to support the CADC makes some sense, but it does highlight an interesting problem - the need to cleanly delineate between physical inputs (joystick axes/buttons, keyboard commands) virtual/logical inputs (virtual yokes, switches, thrust levers, etc) and aircraft system state (virtual hydraulics, electrics, etc.)

In an ideal world you'd have something like, physical input -> translation -> virtual input -> virtual avionics/hydraulics/electrics -> FDM.

The basic idea of my little script is something to sit at the "translation" layer. The problem is simple, we don't all have 100% complete cockpit mockups for the aircraft we fly, so there needs to be a way to map physical inputs from the devices we DO have to the input values that drive the aircraft systems and FDM. At the moment, the layers aren't always cleanly delineated, and the "virtual input" layer is not always a one-to-one map for the controls of a real aircraft (which I think would be ideal, and something to aim at.)

Let me give you an example:

One of the reasons I wrote Aeris was to support dual inputs on a single instance of Flightgear, and I'm breaking my own ideal design by doing it in the "translation" layer (there doesn't seem to be a better way to do it at the moment.) Consider your garden variety Airbus - the real aircraft has two sidesticks so I'd argue it should have two stick inputs (left and right) in the "virtual input layer." The translation layer would then map a physical input to either virtual stick and you could fly the plane. If you have two physical inputs you could drive both (and see both virtual sticks waggling independently in cockpit view.)

Make sense? The same idea could be applied to thrust levers, since the reverse levers are a separate degree of freedom on the phsycial inputs of a real cockpit, there should be a separate axis in the virtual input layer. And so on ...

Okay, let me gush for a second. I love the new-and-improved JSBsim F-14 (thanks Richard!) the flight dynamics are just gorgeous, and it's a fun plane to fly. Consequently, I fly it almost exclusively (my approach to flight simulation is very much "pick a plane and practice, practice, practice") I've just laid hands on a 3D printer, and I've got some nifty dual concentric axis, 360 degree piezo meter movements. I'm considering cutting myself a half circle of plyboard and making myself a miniaturised "desk sized" F-14 cockpit mockup. This is what led to the train of thought that "if every degree of freedom in the cockpit controls of the real aircraft is faithfully represented in the property tree it makes it much easier to build a physical cockpit and make every switch and lever do what it's supposed to."

I've been mulling over various related ideas for a while. It'd be ultra cool to have a unified approach where each cockpit input has it's own branch in the property tree that defines things like "is this a switch, button, lever or axis?" "how many steps does it have?" "can this axis move on it's own (trim wheels, force feedback, autothrottle driven thrust levers, etc)" and "are there any physical interlocks that affect this input?" These are long term ideas, but its good to have direction :D It would certainly be great for obsessive sim geeks who want to build a real cockpit, or companies that want to use Flightgear as the basis for a level D sim!

So, getting back to the F-14 flaps. It depends on the behaviour of the aircraft: does the CADC cause the flaps lever to move? If it doesn't then the position of the flaps lever simply is what it is and the effect of the CADC should be shunted outside the "physical input -> translation -> virtual input" layers. Otherwise it's more complex! :)

Anyway, that pretty much sums up my thoughts. Any ideas/observations?
XH.
* Remember, keep the blue side up and avoid the cumulo-granitus!
XrayHotel
 
Posts: 31
Joined: Thu Sep 03, 2015 6:33 am
Callsign: XH

Re: Improvements to flight control subsystem

Postby Johan G » Thu Sep 17, 2015 12:33 am

XrayHotel wrote in Wed Sep 16, 2015 9:27 pm:Actually the need to support the CADC makes some sense, but it does highlight an interesting problem - the need to cleanly delineate between physical inputs (joystick axes/buttons, keyboard commands) virtual/logical inputs (virtual yokes, switches, thrust levers, etc) and aircraft system state (virtual hydraulics, electrics, etc.)
...
So, getting back to the F-14 flaps. It depends on the behaviour of the aircraft: does the CADC cause the flaps lever to move? If it doesn't then the position of the flaps lever simply is what it is and the effect of the CADC should be shunted outside the "physical input -> translation -> virtual input" layers. Otherwise it's more complex! :)

You most probably have already been looking around in the property tree using the property browser. In general there are some levels of abstraction/interfaces in FlightGear already, but apart from the controls (input) and the aircraft's responses (output), most of those levels are up to the choice of FDM (YASim, JSBSim or an external one), the degree of systems modeling and the amount of abstraction/encapsulation the author put into the aircraft (in essence its architecture).

Hypothetically it is possible to encapsulate most of the systems (into "black boxes") and use the property tree as an interface between them, so that you for example could borrow an MCP ([autopilot] mode control panel) from one aircraft to another and "just" connect it to the property tree of that aircraft or replace a system with an improved one. Unfortunately aircraft authors often focus on ease of implementation rather than ease of interchangeability and ease of maintenance, though very rarely intentionally. This has the effect that systems, in particular in complex aircraft such as for example airliners, tend to both be more entangled with each other as well as end up being bigger "black boxes" than preferable.

XrayHotel wrote in Wed Sep 16, 2015 9:27 pm:It'd be ultra cool to have a unified approach where each cockpit input has it's own branch in the property tree that defines things like "is this a switch, button, lever or axis?" "how many steps does it have?" "can this axis move on it's own (trim wheels, force feedback, autothrottle driven thrust levers, etc)" and "are there any physical interlocks that affect this input?"

Most of that information is already there. However, it is interspersed throughout the files, and it is often not that easy to follow up what actually affects each of the properties. For example you might see in the <aircraft>-set.xml file that one property represent an on/off switch, while you might see in a nasal file that another property represent a five position switch. Finding and reading an aircraft's documentation and flight manual will help a bit, as getting to know the aircraft might help one know what to look for.
Low-level flying — It's all fun and games till someone looses an engine. (Paraphrased from a YouTube video)
Improving the Dassault Mirage F1 (Wiki, Forum, GitLab. Work in slow progress)
Some YouTube videos
Johan G
Moderator
 
Posts: 6634
Joined: Fri Aug 06, 2010 6:33 pm
Location: Sweden
Callsign: SE-JG
IRC name: Johan_G
Version: 2020.3.4
OS: Windows 10, 64 bit

Re: Improvements to flight control subsystem

Postby Philosopher » Thu Sep 17, 2015 1:08 am

I've struggled with this problem a little bit... what I ended up doing was assigning sort-of priority to the different ways to articulate a control change - e.g. if I was using a throttle axis for flaps, a (joystick) button would not be able to control flaps. I've posted my code here if you want to poke around (see controls.nas): https://github.com/NasalMusician/FGCyborg-X. (Warning: it isn't complete, and I haven't actively worked on it for over a year.) If you want to see my attempts at providing a control interface for each individual aircraft, see https://github.com/NasalMusician/FGCybo ... zation.xml (I recognize that in an ideal world the aircraft would operate in a more compatible way, but I decided to just go with it).

btw: aer is the word for air in Latin, aeris would be the genitive case, see: http://wiktionary.org/wiki/aer#Latin
Philosopher
 
Posts: 1593
Joined: Sun Aug 12, 2012 7:29 pm

Re: Improvements to flight control subsystem

Postby Richard » Fri Sep 18, 2015 7:57 pm

XrayHotel wrote in Wed Sep 16, 2015 9:27 pm:
Code: Select all
# whoever wrote the F-14 flaps code needs to be shot ...
# AFAICT it's replacing the pertient functions in controls.nas o_o

How did that comment get in there? Let me rephrase that "I probably wouldn't do it that way." Ahem... :mrgreen:

In an ideal world you'd have something like, physical input -> translation -> virtual input -> virtual avionics/hydraulics/electrics -> FDM.

...

I've been mulling over various related ideas for a while. It'd be ultra cool to have a unified approach where each cockpit input has it's own branch in the property tree that defines things like "is this a switch, button, lever or axis?" "how many steps does it have?" "



Firstly thanks for your gushiness about the F-14 flight model - I'm always happy to hear this -)

For my part I'm convinced that my F-14 is one of the most accurate aerodynamic models in any desktop simulator; largely because the data that AFWAL released in TR80-3141 is of good quality and covers a large range (of AoA). Although I had to figure out the -ve AoA so that isn't as correct but it does follow the right trends and I believe it's about right.

The CADC doesn't move the lever in the cockpit, or at least when I did the CADC I'm sure I checked this and made the JSBSim model work correctly. The whole flaps, spoilers and manual wing sweep are full of interlock rules and deflection schedules that makes it hard to model accurately; but I'm pretty certain that these interlocks don't stop the pilot from moving the controls just the surfaces from moving. For example the aux/main flaps are modeled correctly so at > 22 degrees of sweep you'll only get the main flaps and only the aerodynamic effects from the main flaps.

The basic design in the core is that /controls/<area> are mapped to aircraft controls; these are then turned into positions by the fdm. The input layer in FG maps items to /controls as appropriate, and there is Nasal bindings on top to write values into /controls (e.g. from the kbd/joystick)

The good news is that most of what you want is already there.

Buliding on the principle that most aircraft have a similar set of basic controls; but what we don't have (or at least I'm not aware of anything) is a complete specification covering all aircraft of what values should be in /controls (and subtrees); so generally (or least what I've seen) things that don't fall into the basic set tend to be named as the aircraft modeler sees fit. However it appears that 12 years ago Erik came up with a pretty comprehensive list in the core. refer to the standard bindings in the main source module at controls.cxx::bind().

The difficulty starts when trying to map items that aren't standard; or when standard items aren't known (or weren't standard at the time) and the aircraft models them in a "get something that works" sort of way.

I believe that the fix for the F-14 flaps is reasonably straight forwards, there is a /sim/flaps which is picked up by the Nasal control bindings (which is effectively a translation layer above controls). see http://sourceforge.net/p/flightgear/fgdata/ci/next/tree/Nasal/controls.nas

I'll fix the F-14 to use the correct bindings and remove the non standard way it's done (for flaps and wingsweep). This reminds me that I need to ask on here for objections to removing the YASim version as the JSBSim version sits alongside it and there is much fiddliness to support both models and I can't think of a good reason to keep the (wildly wrong) YASim model (but I am biased).

It also looks like the flaps and wing sweep are completely variable (i.e. these aren't stepped in the aircraft) - there are calibration but I can't see any evidence of the stepping that you see in other aircraft.

There are some improvements that I need to do to the manual wing sweep (as when I did this I didn't really understand how to properly wire up a cockpit so I bound it to the mousewheel over the gauge when I should have bound it to the wingsweep lever on the throttle quadrant and modelled the interlock (cover)), I will hopefully get this done soon.
Richard
 
Posts: 810
Joined: Sun Nov 02, 2014 11:17 pm
Version: Git
OS: Win10

Re: Improvements to flight control subsystem

Postby XrayHotel » Thu Sep 24, 2015 11:28 pm

Richard,

Thanks for the reply, this is what makes Flightgear great - developer interest! :)

Regarding the flaps, it isn't terribly important really - how it's implemented is just a matter of code purity. I have noticed one little glitch though, Aeris displays the degree of flaps extension as a percentage. I've noticed that it goes "flaps 50%, 60%, 70%, 80%, 89%, 99%, 100%" which doesn't sound right. Going back the other way gives a different sequence, "90%, 80%, 70%, 60%, 50%, 40%."

Again, thankyou for the F-14 upgrades - other aircraft don't feel quite so real now! I've started having a cursory dig through the code to get a handle on how various things work. One thing I'm interested in implementing is the DLC feature as it would make landing substantially less hairy!

I'd also love to make it possible to induce a compressor stall and flameout at appropriate points in the envelope - for extra white-knuckle points... I like to live dangerously ;)
XH.
* Remember, keep the blue side up and avoid the cumulo-granitus!
XrayHotel
 
Posts: 31
Joined: Thu Sep 03, 2015 6:33 am
Callsign: XH

Re: Improvements to flight control subsystem

Postby sim » Sat Sep 26, 2015 11:07 pm

...love to make it possible to induce a compressor stall and flameout at appropriate points in the envelope - for extra white-knuckle points... I like to live dangerously ;)


Just adjust fuel levels to last around ten minutes! Then get airborne and cope with the inevitable emergency! :shock:
User avatar
sim
 
Posts: 1431
Joined: Tue Jun 30, 2009 3:13 pm
Location: Shropshire England
Callsign: Fly4Fun
Version: 0.9.10 up
OS: 64 Win 10 HD6450

Next

Return to Hardware

Who is online

Users browsing this forum: No registered users and 2 guests