Programming - Getting data from Falcon4
![]()
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
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
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
![]()