Howto:Write a fuel system in JSBSim

From FlightGear wiki
Jump to navigation Jump to search

As of FlightGear 2.4.0, JSBSim aircraft have a new tank-property: propulsion/tank[n]/external-flow-rate-pps. Setting this property will either draw fuel from (value < 0), or insert fuel into (value > 0) the respective tank. By using this property, one can simulate complex fuel systems. Note: [pps] stands for pounds per second.

A fuel system file

First we create a system file, in which we will write the actual system. It's advised to save this file as Aircraft/.../Systems/fuel.xml.

<?xml version="1.0"?>  

<system name="fuel"> 
	<!-- we will write the system here -->
</system>

We link the main FDM to this system by adding the following line, somewhere in your aircraft's FDM (I advise to put it below </propulsion>).

<system file="fuel"/>

Notice how "fuel" refers to Aircraft/.../Systems/fuel.xml.

Fuel handling in JSBSim

To make JSBSim handle the fuel system during simulation we create one or more channels in the system file. Within these we can use the different control components available in JSBSim.

When using propulsion/tank[n]/external-flow-rate-pps there is no automatic detection if the tank is full or empty. Fuel filling an already full tank will just disappear, creating a "leak". Fuel drawn from an empty tank will make new fuel magically appear in the system. In most cases we will have to make checks to avoid these problems.

Two control components that is useful in a fuel system are summers and switches.

Summers

To calculate the total fuel-flows to/from each tank, we need to sum up the independent flows. This must be done for each single tank, but the principle is the same every time and simple to understand. Every flow that transports fuel from a tank should get a minus sign.

This summer calculates the total flow to or from tank[0].

<summer>
	<input>-propulsion/tank[0]/external-flow-rate/jettison</input>		<!-- jettison -->
	<input>-propulsion/tank[0]/external-flow-rate/tank[1]</input>		<!-- to tank 1 -->
	<input>-propulsion/tank[0]/external-flow-rate/tank[2]</input>		<!-- to tank 2 -->
	<input>-propulsion/tank[0]/external-flow-rate/tank[3]</input>		<!-- to tank 3 -->
	<input>propulsion/tank[1]/external-flow-rate/tank[0]</input>		<!-- from tank 1 -->
	<input>propulsion/tank[2]/external-flow-rate/tank[0]</input>		<!-- from tank 2 -->
	<input>propulsion/tank[3]/external-flow-rate/tank[0]</input>		<!-- from tank 3 -->
	<output>propulsion/tank[0]/external-flow-rate-pps</output>
</summer>

Switches

To turn the flow on or off we need a switch.

This switch draws 5 lbs/s from tank[0] until its empty as long as the collector vale is open.

    <switch>
      <default value="-5"/>
      <test logic="OR" value="0">
        propulsion/tank[0]/collector-valve EQ 0
        propulsion/tank[0]/pct-full LE 0
      </test>
      <output>propulsion/tank[0]/external-flow-rate-pps</output>
    </switch>

Examples

Jettison switch

The /controls/fuel/fuel-to-remain property controls the amount of fuel (in lbs) that should be kept a board. When the total-fuel-level reaches that level, fuel dumping will auomatically stop. Fuel dumping can also be stopped by closing the jettison valves (disable /controls/fuel/dump-valve).

<switch>
	<default value="0"/>
	<test logic="AND" value="12.25">
		/controls/fuel/dump-valve == 1
		propulsion/total-fuel-lbs gt /controls/fuel/fuel-to-remain
	</test>
	<output>propulsion/tank[0]/external-flow-rate/jettison</output>
	<output>propulsion/tank[2]/external-flow-rate/jettison</output>
	<output>propulsion/tank[3]/external-flow-rate/jettison</output>
	<output>propulsion/tank[7]/external-flow-rate/jettison</output>
</switch>

The output properties should be added as inputs to the respective summers. Add a minus sign, as the fuel is taken from the tank.

Two tanks and a collector tank

A system with two tanks (tank[0] and tank[1]) feeding a collector tank (tank[2]). The engine is fed from the collector tank. There is valves between each of the tanks and the collector that can be set open or closed. Negative Gs cut the supply to the collector.

The system consists of two properties: propulsion/tank[0]/collector-valve and propulsion/tank[1]/collector-valve that represent the valves and a channel that handles the fuel transfer from the tanks to the collector and the necessary checks.

The fuel transfer using external-flow-rate-pps does not check against if tanks are empty or full. This can create fuel when "fuel" is drawn from an empty tank or a "leak" when fuel is pumped into a full tank. The collector tank in the example has the capacity 130 lbs so making the check against collector tank level greater or equal to 129 instead of against 130 is done to avoid the later kind of leak since the collector tank normally receives more fuel every time step than the engine uses. The 1 lb margin creates a buffer for this difference as the value oscillates around 129 lbs as the switch turns on and off the flow.

Two tanks and a collector tank
<?xml version="1.0"?>

 
<system name="Fuel">
<!-- tank[2] collector tank connected to the engine fuel pump, tank[0] and tank[1] feeds the collector through valves.
Negative Gs (<-0.5g) cuts the supply to the collector tanks -->

   <property value="1">propulsion/tank[0]/collector-valve</property>
   <property value="1">propulsion/tank[1]/collector-valve</property>

  <channel name="Fuel pumping">    

    <switch>
      <default value="-5"/>
      <test logic="OR" value="0">
        propulsion/tank[0]/collector-valve EQ 0
        propulsion/tank[0]/contents-lbs LE 0
        propulsion/tank[2]/contents-lbs GE 129 <!-- 1 lb below capacity -->
        accelerations/Nz LE -0.5
      </test>
      <output>propulsion/tank[0]/external-flow-rate-pps</output>
    </switch>

    <switch>
      <default value="-5"/>
      <test logic="OR" value="0">
        propulsion/tank[1]/collector-valve EQ 0
        propulsion/tank[1]/contents-lbs LE 0
        propulsion/tank[2]/contents-lbs GE 129 <!-- 1 lb below capacity -->
        accelerations/Nz LE -0.5 
      </test>
      <output>propulsion/tank[1]/external-flow-rate-pps</output>
    </switch>

    <summer>
      <input>-propulsion/tank[0]/external-flow-rate-pps</input>
      <input>-propulsion/tank[1]/external-flow-rate-pps</input>
      <output>propulsion/tank[2]/external-flow-rate-pps</output>
    </summer>  

  </channel>      

</system>

Spitfire: Carburetor engine

Spitfire II fuel system. This systems have properties for damage, leaks and simulates the carburetor as a small tank to handle fuel starvation at negative Gs. The Spitfire has two interconnected tanks with fuel flowing from an upper tank to a lower and both connected to the engine through cocks. The layout of the system can be seen here.

    <?xml version="1.0"?>

     
    <system name="Fuel">

       <property value="0"> /fdm/jsbsim/fuel/tank_top_cock</property>
       <property value="0"> /fdm/jsbsim/fuel/tank_bottom_cock</property>
       <property value="1"> /fdm/jsbsim/fuel/damage</property>
       <property value="0"> /fdm/jsbsim/fuel/priming</property>
       <property value="0"> /fdm/jsbsim/fuel/leak_top_tank</property>
       <property value="0"> /fdm/jsbsim/fuel/leak_bottom_tank</property>
       
       <channel name="carburetor">

          <!-- Total Carburetor Flow Rate in pps -->         
          <summer name="fuel/float_chamber-pps">
             <input>fuel/pump_flow_to_float-pps</input>
             <input>/fdm/jsbsim/fuel/priming</input>         
             <input>-propulsion/engine/fuel-flow-rate-pps</input>
             <output>propulsion/tank[0]/external-flow-rate-pps</output>      
          </summer>      

       </channel>   

       <channel name="Fuel Pump">
       
          <!-- float throttle valve position -->         
          <aerosurface_scale name="fuel/float_throttle-valve-cmd">
             <input>propulsion/tank[0]/contents-lbs</input>
             <zero_centered>false</zero_centered>
             <domain>
                <min>0.0</min>
                <max>0.5</max>
             </domain>                  
             <range>
                <min>1.0</min>
                <max>0.0</max>
             </range>
          </aerosurface_scale >      
          
          
          <!-- Fuel Flow into Float Chamber in pps -->
          <switch name="fuel/flowing_from_tank">
             <default value="0"/>
             <test logic="AND" value="fuel/float_throttle-valve-cmd">
                accelerations/Nz GE -0.5
                <test logic="OR">
                   <test logic="AND">
                      fuel/tank_top_cock EQ 1               
                      propulsion/tank[1]/contents-lbs GT 0
                   </test>            
                   <test logic="AND">
                      fuel/tank_bottom_cock EQ 1
                      propulsion/tank[2]/contents-lbs GT 0
                   </test>      
                </test>
             </test>               
          </switch>

          <fcs_function name="fuel/pump_flow_to_float-pps">
             <function>
                   <product>
                      <property>fuel/flowing_from_tank</property>
                      <table>      <!-- Fuel pump in pps -->
                         <independentVar>propulsion/engine/engine-rpm</independentVar>
                         <tableData> <!-- Fuel pump curve -->               
                            0      0
                            100      1
                            3000   10                  
                         </tableData>
                      </table>
                      <property>fuel/damage</property>
                      <value>0.12</value> <!-- full throttle max fuel demand in pps  -->
                   </product>            
             </function>
             <output>fuel/pump_flow_to_float-pps</output>   
          </fcs_function>   
          
       </channel>      
          
          
       <channel name="Tanks">
             
          <!-- Fuel flow to be divided aross a number of tanks -->      
          <fcs_function name="fuel/flow_from_tank">
             <function>
                <quotient>
                   <property>fuel/pump_flow_to_float-pps</property>
                   <sum>
                      <property>fuel/tank_top_cock</property>
                      <property>fuel/tank_bottom_cock</property>
                   </sum>
                </quotient>               
             </function>
             <output>fuel/flow_from_tank</output>         
          </fcs_function>         

       </channel>   

          
       <channel name="Top Tank">
       
          <!-- top_bottom_transfer -->            
          <switch name="fuel/top_bottom_transfer">
             <default value="0"/>
             <test logic="AND" value="1">
                propulsion/tank[1]/contents-lbs GT 0
                propulsion/tank[2]/contents-lbs LT 322
                accelerations/Nz GE -0.5
             </test>         
          </switch>   
             
          <!-- Fuel tranfer rate in pps-->
          <fcs_function name="fuel/top_bottom_transfer-pps">
             <function>
                <product>
                   <property>fuel/top_bottom_transfer</property>            
                   <sum>
                      <property>fuel/from_bottom_tank</property>
                      <table>      <!-- Liquid finds it's own level -->
                         <independentVar>propulsion/tank[2]/contents-lbs</independentVar>
                         <tableData>
                            319      1
                            322      0       
                         </tableData>
                      </table>
                   </sum>
                </product>               
             </function>
             <output>fuel/top_bottom_transfer-pps</output>   
          </fcs_function>   

          <!-- Top Tank Fuel Flow Rate in pps -->         
          <switch name="fuel/from_top_tank">
             <default value="0"/>
             <test logic="AND" value="fuel/flow_from_tank">   
                fuel/tank_top_cock EQ 1
                accelerations/Nz GE -0.5   
             </test>      
          </switch>

          <!-- Total Top Tank Fuel Flow Rate in pps -->         
          <summer name="fuel/total_from_top_tank-pps">
             <input>-fuel/top_bottom_transfer-pps</input>
             <input>-fuel/from_top_tank</input>
             <input>-fuel/leak_top_tank</input>
             <output>propulsion/tank[1]/external-flow-rate-pps</output>            
          </summer>
          
       </channel>
       
       <channel name="Bottom Tank">         
                
          <!-- Bottom Tank Fuel Flow Rate in pps -->            
          <switch name="fuel/from_bottom_tank">
             <default value="0"/>
             <test logic="AND" value="fuel/flow_from_tank">
                fuel/tank_bottom_cock EQ 1
                accelerations/Nz GE -0.5         
             </test>
          </switch>

          <!-- Total Bottom Tank Fuel Flow Rate in pps -->         
          <summer name="fuel/total_from_bottom_tank-pps">
             <input>fuel/top_bottom_transfer-pps</input>
             <input>-/fdm/jsbsim/fuel/priming</input>
             <input>-fuel/from_bottom_tank</input>
             <input>-fuel/leak_bottom_tank</input>         
             <output>propulsion/tank[2]/external-flow-rate-pps</output>            
          </summer>
          
       </channel>      

    </system>

Douglas DC3 (2 engines, 4 tanks)

The real aircraft has two engines and four tanks.Two tank selectors (one per engine) allows each engine to be fed by each tank, separately and one tank at a time for a given engine. The diagram of the fuel system can be seen in this document. For FG, the engines are numbered [0] and (1], the 'true' feed tanks [0], [1], [2], [3]. Each tank selector is a 5-positions ('OFF' + 4 tanks) rotary switch. Its positions are numbered -1 (= 'OFF'), 0, 1, 2, 3, respectively for 'no tank selected', tank[0], [1], [2], [3].

Prerequisite: A nasal code logic says:

  • if an engine[N] boost-pump is "ON", and fuel-level > 4 lbs in the tank selected for this engine[N] ==> fuel-pressure = 16 psi
  • else, if engine[N] is running, and fuel-level > 4 lbs in the tank selected for this engine[N] ==> fuel-pressure = 14.5
  • else of all that, fuel-pressure = 0

Hence, fuel pressure is a a good criteria to tell that an engine is fed or not, i.e. if it is connected to a tank, if this tank is not empty, with a running pump (or not).

(This function above could probably be implemented by XML)

For the fuel system presented here, two small collector tanks [4] and [5] (each collector tank feeding one engine) have been added. In JSBSim, for this multi-engine aircraft (differently from a single-engine one), these collector tanks are necessary to have each engine be separately fed by its selected 'true' tank and selectively stop if the tank runs out of fuel. The collector tanks are fed by the selected tanks (through rotary switches for left and right engines) and have their level kept constant. This functioning is close to a constant level carburetor.

The FDM contains the following lines which set the tanks. Notice that the engines are written as fed by the collector tanks ([4] and [5]), which themselves will be fed by the selected 'true' tanks ([0] to [3]).

 <propulsion>
   <engine file="DC3_engine"> <!-- number 0 -->
    .........
    <feed>4</feed>
    .........
  </engine>

   <engine file="DC3_engine"> <!-- number 1 -->
    .........
    <feed>5</feed>
    .........
  </engine>

  <tank type="FUEL" number="0">
     .........
     <capacity unit="LBS"> 1202 </capacity>
     <contents unit="LBS"> 600 </contents>
  </tank>

  <tank type="FUEL" number="1">
      ...........
     <capacity unit="LBS"> 1210 </capacity>
     <contents unit="LBS"> 600 </contents>
  </tank>

  <tank type="FUEL" number="2">
     ...........
     <capacity unit="LBS"> 1210 </capacity>
     <contents unit="LBS"> 600 </contents>
  </tank>

  <tank type="FUEL" number="3">
     ...............
     <capacity unit="LBS"> 1202 </capacity>
     <contents unit="LBS"> 600 </contents>
  </tank>

  <tank type="FUEL" number="4">
     <!-- Collector Tank (left engine) -->
     ...........
     <capacity unit="LBS"> 0.5 </capacity>
     <contents unit="LBS"> 0 </contents>
  </tank>

  <tank type="FUEL" number="5">
     <!-- Collector Tank (right engine) -->
     ..............
     <capacity unit="LBS"> 0.5 </capacity>
     <contents unit="LBS"> 0 </contents>
  </tank>

 </propulsion>

The Fuel system XML file itself is shown below (can be named fuel.xml, see at the top of this document for how to link the FDM to it).

Collector tanks [4] and [5] have their level kept constant (at 0.25 lb content) by a step-controlled input flow rate (0 or 0.5 pps here). The "ON" value of this step flow rate must be a bit higher than the maximum" fuel-flow-rate-pps" needed by each engine at full power.

Collector tanks [4] and [5] content is a compromise. A large content is secure but it gives an excessive delay between the moment when the 'true' tank runs out of fuel and the engine effectively stops running.

<?xml version="1.0"?>
 
<!--
***********************************************************************************
Douglas DC-3 C47, initial 3D with YASim FDM by Emmanuel BARANGER,
further modifications by PAF team:  http://equipe-flightgear.forumactif.com
 
May 2013, Daniel DUBREUIL, fuel system for JSBSim FDM
with Bomber (Simon), jam007, Jon S. Berndt Assistance. Thank you!
***********************************************************************************
 -->
 
 
<system name="fuel">
 
<!-- *********************************************************************************** -->
<!-- ************ LEFT ENGINE *************** -->
<!-- *********************************************************************************** -->
 
 
   <channel name="Engine 0 Rotary Left Valve Logic">
 
      <!-- Tank 4 level maintenance -->
      <!-- Collector Tank 4 works like a constant-level carburetor for left engine -->
      
      <!-- from selected 'utility' tank to Collector Tank 4 Fuel Flow Rate in pps -->
      <switch name="fuel/through-left-valve">
         <default value="0"/>
         <test logic="AND" value="0.5">
            /controls/fuel/left-valve EQ 0               
            propulsion/tank[0]/contents-lbs GT 0
            /consumables/fuel/tank[4]/level-lbs LT 0.25
            /engines/engine[0]/fuel-psi-norm GT 5
         </test>            
         <test logic="AND" value="0.5">
            /controls/fuel/left-valve EQ 1
            propulsion/tank[1]/contents-lbs GT 0
            /consumables/fuel/tank[4]/level-lbs LT 0.25
            /engines/engine[0]/fuel-psi-norm GT 5
         </test>
         <test logic="AND" value="0.5">
            /controls/fuel/left-valve EQ 2               
            propulsion/tank[2]/contents-lbs GT 0
            /consumables/fuel/tank[4]/level-lbs LT 0.25
            /engines/engine[0]/fuel-psi-norm GT 5
         </test>            
         <test logic="AND" value="0.5">
            /controls/fuel/left-valve EQ 3
            propulsion/tank[3]/contents-lbs GT 0
            /consumables/fuel/tank[4]/level-lbs LT 0.25
            /engines/engine[0]/fuel-psi-norm GT 5
         </test>
         <output>propulsion/tank[4]/external-flow-rate-pps</output> <!-- Tank 4 filling -->
      </switch>
 
      <!-- for Tanks 0, 1, 2, 3 consumption -->

      <!-- from Tank 0 to Collector Tank 4 through Left Valve Fuel Flow Rate in pps -->
      <switch name="fuel/from-tank0-to-tank4">
         <default value="0"/>
         <test value="-fuel/through-left-valve">
            /controls/fuel/left-valve EQ 0      
         </test>
      </switch>      
 
      <!-- from Tank 1 to Collector Tank 4 through Left Valve Fuel Flow Rate in pps -->            
      <switch name="fuel/from-tank1-to-tank4">
         <default value="0"/>
         <test value="-fuel/through-left-valve">
            /controls/fuel/left-valve EQ 1      
         </test>
      </switch>
 
     <!-- from Tank 2 to Collector Tank 4 through Left Valve Fuel Flow Rate in pps -->            
      <switch name="fuel/from-tank2-to-tank4">
         <default value="0"/>
         <test value="-fuel/through-left-valve">
            /controls/fuel/left-valve EQ 2      
         </test>
      </switch>      
 
      <!-- from Tank 3 to Collector Tank 4 through Left Valve Fuel Flow Rate in pps -->            
      <switch name="fuel/from-tank3-to-tank4">
         <default value="0"/>
         <test value="-fuel/through-left-valve">
            /controls/fuel/left-valve EQ 3      
         </test>
      </switch>
 
   </channel>
 
 
 
<!-- *********************************************************************************** -->
<!-- ************ RIGHT ENGINE *************** -->
<!-- *********************************************************************************** -->
 
   <channel name="Engine 1 Rotary Right Valve Logic">
 
      <!-- Tank 5 level maintenance -->
      <!-- Collector Tank 5 works like a constant-level carburetor for right engine -->
      
      <!-- from selected 'utility' tank to Collector Tank 5 Fuel Flow Rate in pps -->
      <switch name="fuel/through-right-valve">
         <default value="0"/>
         <test logic="AND" value="0.5">
            /controls/fuel/right-valve EQ 0               
            propulsion/tank[0]/contents-lbs GT 0
            /consumables/fuel/tank[5]/level-lbs LT 0.25
            /engines/engine[1]/fuel-psi-norm GT 5
         </test>            
         <test logic="AND" value="0.5">
            /controls/fuel/right-valve EQ 1
            propulsion/tank[1]/contents-lbs GT 0
            /consumables/fuel/tank[5]/level-lbs LT 0.25
            /engines/engine[1]/fuel-psi-norm GT 5
         </test>
         <test logic="AND" value="0.5">
            /controls/fuel/right-valve EQ 2               
            propulsion/tank[2]/contents-lbs GT 0
            /consumables/fuel/tank[5]/level-lbs LT 0.25
            /engines/engine[1]/fuel-psi-norm GT 5
         </test>            
         <test logic="AND" value="0.5">
            /controls/fuel/right-valve EQ 3
            propulsion/tank[3]/contents-lbs GT 0
            /consumables/fuel/tank[5]/level-lbs LT 0.25
            /engines/engine[1]/fuel-psi-norm GT 5
         </test>
         <output>propulsion/tank[5]/external-flow-rate-pps</output> <!-- Tank 5 filling -->
      </switch>
 
      <!-- for Tanks 0, 1, 2, 3 consumption -->

      <!-- from Tank 0 to Collector Tank 5 through Right Valve Fuel Flow Rate in pps -->
      <switch name="fuel/from-tank0-to-tank5">
         <default value="0"/>
         <test value="-fuel/through-right-valve">
            /controls/fuel/right-valve EQ 0      
         </test>
      </switch>      
 
      <!-- from Tank 1 to Collector Tank 5 through Right Valve Fuel Flow Rate in pps -->            
      <switch name="fuel/from-tank1-to-tank5">
         <default value="0"/>
         <test value="-fuel/through-right-valve">
            /controls/fuel/right-valve EQ 1      
         </test>
      </switch>
 
     <!-- from Tank 2 to Collector Tank 5 through Right Valve Fuel Flow Rate in pps -->            
      <switch name="fuel/from-tank2-to-tank5">
         <default value="0"/>
         <test value="-fuel/through-right-valve">
            /controls/fuel/right-valve EQ 2      
         </test>
      </switch>      
 
      <!-- from Tank 3 to Collector Tank 5 through Right Valve Fuel Flow Rate in pps -->            
      <switch name="fuel/from-tank3-to-tank5">
         <default value="0"/>
         <test value="-fuel/through-right-valve">
            /controls/fuel/right-valve EQ 3      
         </test>
      </switch>
 
   </channel>
 
<!-- *********************************************************************************** -->
<!-- ************ TOTAL FLOW RATE per TANK *************** -->
<!-- *********************************************************************************** -->
 
     <channel name="Total flow rates">
 
      <summer name="fuel/tank0-flow-rate">
         <input>fuel/from-tank0-to-tank4</input>
         <input>fuel/from-tank0-to-tank5</input>
         <output>propulsion/tank[0]/external-flow-rate-pps</output>
      </summer>
 
       <summer name="fuel/tank1-flow-rate">
         <input>fuel/from-tank1-to-tank4</input>
         <input>fuel/from-tank1-to-tank5</input>
         <output>propulsion/tank[1]/external-flow-rate-pps</output>
      </summer>
 
      <summer name="fuel/tank2-flow-rate">
         <input>fuel/from-tank2-to-tank4</input>
         <input>fuel/from-tank2-to-tank5</input>
         <output>propulsion/tank[2]/external-flow-rate-pps</output>
      </summer>
 
      <summer name="fuel/tank3-flow-rate">
         <input>fuel/from-tank3-to-tank4</input>
         <input>fuel/from-tank3-to-tank5</input>
         <output>propulsion/tank[3]/external-flow-rate-pps</output>
      </summer>
 
   </channel>
 
</system>