Programming - Getting data from Falcon4

The F4 Data

Falcon4 is the only sim I know of which has the unique feature of sharing internal data thru a WIN32 mechanism called shared memory. There is even support for doing input to the sim (VR Headset).

Before we discuss how to get to this data, let's see what's in there. The original Microprose development team did put in the basic data. It is fair to say that this collection is far from complete, and a true simpitter would probably need much more.

Ok, what is actually there:

value type direction value comment
x float  output  feet Position of aircraft in north-south direction 
y float  output feet Position of aircraft in East-West direction 
z float  output feet Elevation of aircraft 
xDot float  output feet/sec Velocity of aircraft in north-south direction
yDot float  output feet/sec Velocity of aircraft in East-West direction
zDot float  output feet/sec Rate of decent/climb 
alpha float  output degrees Angle of attack 
beta float  output degrees Beta 
gamma float  output degrees Gamma 
pitch float  output radians Pitch angle of aircraft 
roll float  output radians Roll angle of aircraft 
yaw float  output radians Yaw angle of aircraft, can also be used as magnetic heading 
mach float  output mach Mach number 
kias float  output knots Indicated airspeed
vt float  output ft/sec True airspeed 
gs float  output G G-force 
windOffset float  output radians Wind delta to FPM 
nozzlePos float  output percent (0-100) Engine nozzle 
internalFuel float  output lbs Internal fuel
externalFuel float  output lbs External fuel 
fuelFlow float  output lbs/hour Fuel flow 
rpm float  output percent (0-103) Engine rpm
ftit float  output degrees °C Forward Turbine Inlet Temperature
gearPos float  output 0=up, 1=down Gear position
speedBrake float  output 0=closed, 1=60 degrees open Speed brake position
epuFuel float  output percent (0-100) EPU fuel
oilPressure float  output percent (0-100) Oil Pressure
lightBits int  output (see Lights below) Cockpit Indicator Lights, each bit in this 32-bit value represents the state of one light
headPitch float  input radians Head pitch offset from design eye
headRoll float  input radians Head roll offset from design eye 
headYaw float  input radians Head yaw offset from design eye 

Lights

name bitmask location comment
MasterCaution 0x1 Brow Lights
TF 0x2
OBS 0x4
ALT  0x8
ALT_1  0x10
ENG_FIRE  0x20
HYD  0x80
OIL  0x100
DUAL  0x200
CAN  0x400
T_L_CFG  0x800
AOAAbove  0x1000 AOA Indexers
AOAOn  0x2000
AOABelow  0x4000
RefuelRDY 0x8000 Refuel/NWS
RefuelAR  0x10000
RefuelDSC 0x20000
FltControlSys  0x40000 Caution Lights
LEFlaps  0x80000
EngineFault  0x100000
Overheat  0x200000
FuelLow  0x400000
Avionics  0x800000
RadarAlt  0x1000000
IFF  0x2000000
ECM  0x4000000
Hook  0x8000000
NWSFail  0x10000000
CabinPress  0x20000000

For those who need a refresher in bit-wise operations, here an example:

If you want to read the state of the Canopy Warning browlight, you need to access the 10th bit from the right. You do this by AND-ing the lightBits value with the hexadecimal value 400 (all zeros but the 10th bit is a one, e.g. 00000000000000000000001000000000). 

The AND operation will filter the 10th bit, and give you the state of this light.

If lightBits contain the value 0x8A29  (1000101000101001), and we AND this with CAN (0x400) it gives us a result of 0x400. A non zero value meaning the Canopy Warning light is lit!

OK, let's do some coding. As I do most of my stuff in Delphi, I will use this for my examples. But I will also give you some hints if you're using C/C++ or VB

The Falcon FlightData Reader Project

As this is NOT a Delphi tutorial, I won't go into details how to setup an application. So, just start with a new application and put in a TTimer.

Disable the timer and put the interval somewhere around 500 ms.

In the OnTimer event we will start reading data from the shared memory and output it to the screen. You can start with some simple labels, and as you're program matures make all kind of fancy graphical representations for your data. I leave this up to you. You can even write a client/server application, where the server collect the data and sends it to a client running on another PC.

But before we can access the data we need to open the shared memory file. This file is present on you system as soon as you start Falcon4. The file is called 'FalconSharedMemoryArea' and does only exists in memory. If you want deeper insight in the API calls I use, look here:  Inter-Process Communication using Shared Memory.

So in the CreateForm event we could try to open the shared memory file, but I myself prefer to use a button to open the file. Whatever method you choose you need to do the following:

declare the following global variables:

var
  gSharedMemHandle: THandle;
  PFD: PFlightData;

Now we need to open the shared memory file. Add a button we will use to connect to Falcon 4 and in your OnClick handler write:

  gSharedMemHandle := OpenFileMapping(FILE_MAP_READ, TRUE, 'FalconSharedMemoryArea');
  if (gSharedMemHandle <> 0) then
    begin
      PFD := MapViewOfFile(gSharedMemHandle, FILE_MAP_READ, 0, 0, 0);
      if (PFD <> nil) then
        begin
          Label1.Caption := 'Falcon4 shared memory file opened.';
          Timer1.Enabled := true; // start reading data
        end
      else
        begin
          Label1.Caption := 'Failed to open Falcon4 shared memory file.'
          CloseHandle(gSharedMemHandle);
        end;
    end;

As you see I use a TLabel to display the result of this button's action. Now add another button and use this one to close the file. In this button's OnClick handler write:

  // Close shared memory area
  if (PFD <> nil) then
    begin
      Timer1.Enabled := false; //stop referencing PFD because we're destroying it.
      UnmapViewOfFile(PFD);
      PFD := nil;
    end;
  CloseHandle(gSharedMemHandle);
  Label1.Caption := 'Falcon4 shared memory file closed.'

Now you may want to test this. Before you can compile this you must first add the declaration of PFlightData. Download this unit (FD.pas) and add it to your project. Don't forget to add it to your uses clause of you main unit.

Now run it and see it fail! Of course we can't open the file successfully until Falcon4 is started. Here I encountered the following problem: If F4 runs full screen, how can I access my shared memory reader? Well, just start Falcon4 with the command line option -window and it starts in a window. Now you can see both. Try it and see if you can successfully open the shared memory file.

When all is well we're to proceed to actual reading some of the data. We'll put this stuff in the OnTimer event:

  Label2.Caption:= Format('X:     %15.2f Y:      %15.2f Z:     %15.2f', [PFD^.x, PFD^.y, PFD^.z]);
  Label3.Caption:= Format('XDot:  %15.2f YDot:   %15.2f ZDot:  %15.2f', [PFD^.xDot, PFD^.yDot, PFD^.zDot]);
  Label4.Caption:= Format('Alpha: %15.2f Beta:   %15.2f Gamma: %15.2f', [PFD^.alpha, PFD^.beta, PFD^.gamma]);

For the C/C++ there is good starting point in the DEBUG.ZIP file. 

For VB it is very similar

First declare:

Public Type FlightData 
  x As Single
  y As Single
  z As Single
  xDot As Single
  yDot As Single
  zDot As Single
  alpha As Single
  beta As Single
  gamma As Single
  pitch As Single
  roll As Single
  yaw As Single
  mach As Single
  kias As Single
  vt As Single
  gs As Single
  windOffset As Single
  nozzlePos As Single
  internalFuel As Single
  externalFuel As Single
  fuelFlow As Single
  rpm As Single
  ftit As Single
  gearPos As Single
  speedBrake As Single
  epuFuel As Single
  oilPressure As Single
  lightBits As Integer
End Type
Global FD As FlightData

Declare the API functions:

Declare Function OpenFileMapping Lib "kernel32" Alias "OpenFileMappingA" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal lpName As String) As Long
Declare Function MapViewOfFile Lib "kernel32" (ByVal hFileMappingObject As Long, ByVal dwDesiredAccess As Long, ByVal dwFileOffsetHigh As Long, ByVal dwFileOffsetLow As Long, ByVal dwNumberOfBytesToMap As Long) As Long
Declare Function UnmapViewOfFile Lib "kernel32" (lpBaseAddress As Any) As Long
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

And use them:

hSharedMem = OpenFileMapping(FILE_MAP_READ, True, "FalconSharedMemoryArea")
If hSharedMem Then
  PSharedMem = MapViewOfFile(hSharedMem, FILE_MAP_READ, 0, 0, 0)
Else
  // unable to open the shared memory file
  CloseHandle (hSharedMem)
End If

Reading data:

CopyMemory FD, ByVal PSharedMem, Len(FD)
Form1.TextBox1.Text = FD.rpm

And don't forget to close the file when you're done.

That's all there is to it! All you need to expand this to a slick, graphical program (glass cockpit??) are 'normal' programming skills.

This is my own (always under construction) Flight Data Reader


(click to see larger image)

 

The Future

As I said earlier, the data available is not close to what we really would want to see. 

Read on about new data in eFalcon 109