Development Update: How to Define and Request Object-Data

With SimConnect you can request the simulator to send you data, and keep sending it when it changes, or in a specified cadence. The process for this is pretty straightforward, although not entirely without its challenges.

The steps you need to go through are:

  • First you chose a “DefineID”, identifying the block of data you whish to detail its contents of.
  • Then you build (out) the “Definition” block, by calling “SimConnect_AddToDataDefinition()” for each individual variable you wish to receive. This should include a type for the exchange format, and a “unit” if appropriate. The type specifies the encoding for the value, so you can get numbers as a “float” (32-bit floating point number) or a “double” (64-bit floating point number), although your choices will be limited by the actual units involved. The “unit” allows you to query a value in a different unit than it is kept in the simulator, for example converting from feet to meters.
  • When the block contains everything you want, you call “SimConnect_RequestDataOnSimObject()” to tell the simulator how often you want to receive this block, and what transport format to use.

The “AddToDataDefinition” calls can fail, but generally only do when you make a big mistake, such as using an invalid “handle”. If you make the mistake of asking for data the simulator cannot deliver or a unit for which it has no conversion, it is not the static SimConnect library that will complain, but rather the simulator itself, which means we need to wait for the request to be sent over, and then for a (potential!) exception message related to this particular call. This is coordinated using “SendID“s, so for every call that is translated into a message going into the simulator you need to ask SimConnect what the SendID was. Then, if an exception comes in, you know you did something wrong… if you can still remember what that was. The request for the actual data uses a RequestID, but it also gets a related SendID for the possibility that an exception is raised.

Now compare that approach with the following:

    public class AircraftData
		[DataDefinition("ATC TYPE", Type = DataType.String256)]
        public string Type { get; set; }

		[DataDefinition("ATC MODEL", Type = DataType.String256)]
		public string Model { get; set; }

		[DataDefinition("ATC ID", Type = DataType.String256)]
		public string Id { get; set; }

		[DataDefinition("ATC AIRLINE", Type = DataType.String256)]
		public string Airline { get; set; }

		[DataDefinition("ATC FLIGHT NUMBER", Type = DataType.String256)]
		public string FlightNumber { get; set; }

		[DataDefinition("TITLE", Type = DataType.String256)]
		public string Title { get; set; }

		[DataDefinition("NUMBER OF ENGINES", Units = "Number", Type = DataType.Int32)]
		public int NumberOfEngines { get; set; }

		[DataDefinition("ENGINE TYPE", Units = "Number", Type = DataType.Int32)]
		public int EngineType { get; set; }

Here I define a C# class, and annotate it with the information needed to build the definition. I am not interested in choosing DefineIDs or iterating over the fields. Let the library do that for me and announce the block to the simulator. Now when I want the data, all I want to have to do, is something like this:

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

That is it! Better still, if I want to put the information in the status bar at the bottom of my window, I would like to do something like this:

DataManager.Instance.RequestData<AircraftData>(ObjectDataPeriod.PerSecond, onlyWhenChanged=true)
                    .Subscribe((AircraftData data) => this.Dispatcher.Invoke(UpdateStatusBar(data)));

This asks for the data not once, but once every second, and then only if there is a change. The resulting stream will be pumped into a lambda function that updates the status bar on the window’s dispatcher thread.

Is this already working?

The first one (just request the data once) I’ve got working, using it as a test to experiment with the techniques needed. The errors and crashes I have had while building this learned me:

  1. SimConnect is not thread-safe. If two parts of my application call “SimConnect_Something()” at (more or less) the same time, you can get very amusing results, like duplicate SendIDs or faulty data. The static library is using static buffers (sic) that can get overwritten while in use. The simple solution is to put a lock around any such call, so they get serialized. Since I want to embrace parallel behavior rather than avoid it, this is not ideal, so I’ll try to optimize any involvement of SimConnect so the lock is released as soon as possible. Note that means involves the handling of incoming messages, because we get passed a pointer into a static buffer with the data. So copy the data as fast as I can, release the lock, and have another thread take care of the actual processing of that message.
  2. SendIDs are a pain. I need to keep track of them so incoming Exceptions can be matched to them, and clean up any that are no longer relevant. For example, I’m not expecting to get complaints about “AddToDataDefinition” calls when the actual data request has been sent off, but they might. It would simply mean that the block of data has less elements than expected, because those fields were not added. That in turn means I cannot assume incoming data will conform to the full definition. Tagged format should help preventing garbage data, so maybe I should use that by default. Note that this happening “in production” means the simulator cannot deliver the data I asked, so maybe it is FSX instead of MSFS, or MSFS instead of Lockheed Martin Prepar3D. (Think “asking for military mission data”)
  3. Surviving disconnects is “interesting”. Both on the CsSimConnect level, because it needs to clear out its administration, as well as on the client side. Is re-defining data blocks and re-requesting data something the library needs to automagically do for us? Or can we expect the application to do this?

Until we meet again

I’ll keep posting these kinds of stories, to keep you up-to-date on development, but also to help me structure my thoughts on how to design this. See you next time!

Leave a Reply

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

You are commenting using your 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