January 2022 update: Events and data flying around

Welcome to 2022! Sorry. to be silent for so long, but I actually have made progress, so let’s look at what changed. Also, we’ll have a look at what is next.

The AutoPilotController and EventManager

At the specific request of a certain flyer, I worked on polishing a small app to control your autopilot. This took a lot more effort than I planned, due to the simple fact that I have little experience with Windows UI programming using C# and XAML. I wanted the app to have at least a little bit more than just the default Look&Feel and settled on the Material Design toolkit, which I already started using for the CsSimConnectUI demo. This led to researching things like how to make an “always on top” application, so it won’t disappear under the simulator’s window, but also research into storing application settings. I split non-FS-related stuff off into a separate repository named RakisUtil, so that is now the home of the “Rakis.Logger”, “Rakis.Settings”, and “Rakis.Args” projects.

I also had introduced signing on a few of the projects and noticed that caused Visual Studio to complain about the unsigned ones. Then I started looking at certificates for signing Open Source projects and was shocked to learn that there is no code-signing equivalent of LetsEncrypt. Since I am not going to spend 650 euros per year on a hobby project without long-term sponsoring, I’ve kept myself to the VS-generated certificates for the time being. My apologies if that causes trouble. Oh yes, I did notice that this will block publication to the official NuGet repositories, as they require a formally signed package. It looks like they do not like Open Source if it’s not backed by serious sponsors. Sigh.

Anyways, the controller app works nicely and I even added a configuration dialog to allow you to specify the events used for “Speed Hold.” Not all aircraft support this, even in the real world (take the CRJ-900 for example), and the kind of controls that allow you to set it (or whatever comes closest) differs.

Scanning the simulator for airports and aircraft

Returning to the CsSimConnectUI app, I took a fresh look at creating an AI aircraft, specifically one that can be directly controlled by the SimConnect client rather than through a Flightplan. Creating a “Parked AI Aircraft” caused all kinds of weird behavior, which the “Non-ATC AI Aircraft” did not have, and it looks like only that last one is specifically meant to be left without a plan. However, if you want it to start at a specific parking on an airport, you have a problem, because that is only supported through the “SimConnect_AICreateParkedATCAircraft()” call. So I needed to find the coordinates and heading for the parking I wanted to use, and that information requires you to scan the simulator’s scenery files. Documentation on this is not available from either Lockheed Martin or Microsoft, as they expect you to create scenery, not read the compiled BGL files. Luckily the FSDeveloper community has collected a lot of information and tools, and we now have a “SimScanner” library that is capable of reading FSX, Prepar3D, and MSFS-2020 scenery files and defines an SQLite database with the resulting info. It is not complete, but complete enough for what I wanted. Next was to extend the SimScanner library to also collect aircraft files, so you have a full list of all available aircraft. Likewise, it has a lot of room for improvement, but it is extensive enough to allow you to finally create that AI aircraft at a specific parking.

Newest addition: the Flight Recorder!

Let’s finish up with a bit of code. I wrote a small flight recorder, which captures a number of seconds of whatever your aircraft is doing, and allows you to replay that using an AI aircraft. It starts the recording by capturing the title and tailnumber of your aircraft, as well as its position and orientation:

AircraftData aircraft = DataManager.Instance
    .RequestData<AircraftData>()
    .Get();

This uses a class “AircraftData” with a set of annotated fields. I started out by using “InitPosition“, but that is not a requestable simulation variable. Then I split it up and used the “LatLongAlt” and “PBH” structs, which actually are available as “STRUCT LATLONALT” and “STRUCT LATLONALTPBH“, but you immediately notice there is something unusual with the pitch, bank, and heading: they appear not to use their own structure. SimConnect will not complain if you specify the “SIMCONNECT_DATA_PBH” datatype, but always return three times zero. Google wasn’t my friend either, because I only found some very old references to this behavior, which boiled down to “just don’t try.” So, the class has individual fields for everything, and that works like a charm. A JSON formatted file is created with one object per line, and the first line has everything you need to create the AI aircraft. The remainder of the file contains lines with just updated positions and timestamps, captured using:

DataManager.Instance
    .RequestData(data,
                 period: ObjectDataPeriod.PerSimFrame,
                 onlyWhenChanged: true,
                 onNext: () => {
                     haveData = true;
                     seqNr++;
                     timestamp = DateTime.Now;
                 });

The callback sets a boolean so we know when we can start writing data, an updated sequence number so we won’t write unchanged data and the timestamp.

On the playback side, we use an “AircraftBuilder“:

SimulatedAircraft aircraft = AircraftBuilder.Builder(aircraftData.Title)
    .WithTailNumber(aircraftData.TailNumber)
    .AtPosition(aircraftData.Latitude,
                aircraftData.Longitude,
                aircraftData.Altitude)
    .WithPBH(aircraftData.Pitch,
             aircraftData.Bank,
             aircraftData.Heading)
    .OnGround()
    .WithAirSpeed(aircraftData.AirSpeed)
    .Build();
SimulatedAircraft ai = AIManager.Instance.Create(aircraft).Get();

It then reads the remainder of the file, uses the first timestamp to compute an offset compared to the start of replaying, and then sets the data on the AI aircraft when the correct moment arrives:

line = f.ReadLine();
TimeSpan diff = TimeSpan.Zero;
while (line != null)
{
    FlightData data = JsonConvert.DeserializeObject<FlightData>(line);
    if (data == null) {
        Console.WriteLine("Unable to deserialize object.");
        break;
    }
    DateTime now = DateTime.Now;
    Console.WriteLine($"Read data with ts = {data.ts}, now = {now}.");
    if ((diff == TimeSpan.Zero) || ((data.ts + diff) > now)) {
        if (diff == TimeSpan.Zero) {
            diff = now - data.ts;
            Console.WriteLine($"Going to correct for {diff} in time difference");
        }
        Thread.Sleep(data.ts + diff - now);
        DataManager.Instance.SetData(ai.ObjectId, data);
    } else {
        Console.WriteLine("Skipping old data.");
    }
    line = f.ReadLine();
}

I must say I was actually surprised how little work this took, once the underlying library supported all data correctly.

Next Goal: Build a gauge

My old C++ code collection included a working app to put a gauge on a Saitek Flight Instrument Panel. This included a pretty complete implementation of the XML gauge parser and associated stack machine language. If I can revive that code and combine it with what I learned from the AutoPilotController app, we should have a pretty neat demo of an app collecting live data from the simulator. Let’s see how that works out…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s