SimConnect + WASM combined project using VS2019

Chapter 6: Controlling the “A:”-type variables

This is a very long chapter, but there is a “6. Summary” at the end.

A-type variables are the “native MSFS2020 variables” which can be controlled directly by SimConnect (without the need of an extra WASM module). So let’s start with those.

1. Adding a data definition

Whether we want to set a variable with a certain value, or we want to get updated when the variable changes in MSFS2020, the first thing we ALWAYS need to do is to give an ID to the variable. This ID will be called the “DefineID”.

You will see this a lot in SimConnect. Variables or data structures are being identified with an ID in your client. We all know that computers prefer to work with ID’s, because they are a lot faster and easier to use than long strings.

In public bool AddVariable(string sVar) in the object SimConnectHUB, when dealing with an “A:-variable”, the following method is called (“v” is an object of type VarData):

_oSimConnect.AddToDataDefinition(
    (SIMCONNECT_DEFINITION_ID)v.uDefineID,
    v.sName,
    v.sUnit,
    v.scDataType,
    0.0f,
    SimConnect.SIMCONNECT_UNUSED);

In the SimConnect SDK Documentation you can find the explanation for SimConnect_AddToDataDefinition.

The AddToDataDefinition method is doing nothing more than linking the “v.sName, v.sUnit, v.scDataType” with an ID “v.uDefineID” that is unique in my client.

The “v.uDefineID” has been defined in the line of code just preceeding the AddToDataDefinition call:

v.SetID(VarData.AUTO_ID, VarData.AUTO_ID);

This method, when using the parameter “VarData.AUTO_ID” is taking the next available ID and stores it in “v.uDefineID”. The method also gives a value to “v.uRequestID”, which is another unique value that we will use later.

Let’s have a closer look at the signature of AddToDataDefinition:

public void AddToDataDefinition(Enum DefineID, string DatumName, string UnitsName, SIMCONNECT_DATATYPE DatumType, float fEpsilon, uint DatumID);
  • We see that the first parameter has type “Enum”. That is typical in SimConnect functions - rather then using “int” or other variable types, they use “Enum”. Because my ID’s are of type “UInt16”, I have created some enum types to use for typeconversion:
// Some enums for type conversions
private enum SIMCONNECT_DEFINITION_ID { }
private enum SIMCONNECT_REQUEST_ID { }
private enum EVENT_ID { }
  • “v.scDataType” has the type SIMCONNECT_DATATYPE. This is a type defined by SimConnect. You will see in the VarData.ParseVarString that the DataType string is converted in a SIMCONNECT_DATATYPE. Example: “FLOAT64” will be converted to “SIMCONNECT_DATATYPE.FLOAT64”, which is the value used as parameter in AddToDataDefinition.

  • The last 2 parameters are not used in my implementation. Although fEpsilon might be useful in some cases. I suggest you read the SimConnect SDK Documentation if you need more info.

If we call AddToDataDefinition with a variable that does not exist in MSFS2020, then the EventHandler “SimConnect_OnRecvException” would have been called with the exception “UNRECOGNIZED_ID”.

Remark: I’m using a static DefineID and RequestID counter in the object VarData that increases every time I’m calling “SetID”. If a call to AddToDataDefinition fails, I don’t reuse the ID. That means that you can call AddToDataDefinition a maximum of 65536 times before running out of ID’s. But I’m pretty sure that this limit will never be reached.

2. Define your data structure

This is something very specific for Managed Code, but it is extremely important not to forget this step. Although this is a very important step, you don’t find a lot of documentation or more details about it. You have to use the below method to link your DefineID with a data type.

_oSimConnect.RegisterDataDefineStruct<[your data type]>(DefineID);

In my implementation, this method is called immediately after AddToDataDefinition. It uses a switch statement to register the correct data type.

switch (v.scDataType)
{
    case SIMCONNECT_DATATYPE.INT32:
        _oSimConnect.RegisterDataDefineStruct<Int32>((SIMCONNECT_DEFINITION_ID)v.uDefineID);
        break;
    case SIMCONNECT_DATATYPE.INT64:
        _oSimConnect.RegisterDataDefineStruct<Int64>((SIMCONNECT_DEFINITION_ID)v.uDefineID);
        break;
    case SIMCONNECT_DATATYPE.FLOAT32:
        _oSimConnect.RegisterDataDefineStruct<float>((SIMCONNECT_DEFINITION_ID)v.uDefineID);
        break;
    case SIMCONNECT_DATATYPE.FLOAT64:
        _oSimConnect.RegisterDataDefineStruct<Double>((SIMCONNECT_DEFINITION_ID)v.uDefineID);
        break;
    case SIMCONNECT_DATATYPE.STRING256:
        _oSimConnect.RegisterDataDefineStruct<String256>((SIMCONNECT_DEFINITION_ID)v.uDefineID);
        break;
}

You can also use structures containing multiple variables. In that case, you will have to add all the members (variables) of the structure one by one with a call to AddToDataDefinition using the same DefineID. After that, you have to use RegisterDataDefineStruct to register your structure. In my implementation, I use a different DefineID for each variable, because that makes most sense.

When you want to use structures, you have to define them in a specific way as shown below. For strings, you have to use Type Marshaling. Using variable string also requires some extra efforts, which I haven’t used yet. My implementation only uses strings of 256 characters.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct Struct1
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public String title;
    public double latitude;
    public double longitude;
    public double altitude;
};

3. Setting a variable value

Once we have successfully called AddToDataDefinition and RegisterDataDefineStruct, we can set the value of the variable with the following method:

_oSimConnect.SetDataOnSimObject(
    (SIMCONNECT_DEFINITION_ID)vInList.uDefineID,
    0,
    SIMCONNECT_DATA_SET_FLAG.DEFAULT,
    dValue);

In the SimConnect SDK Documentation you can find the explanation for SimConnect_SetDataOnSimObject.

The signature of SetDataOnSimObject looks like this:

public void SetDataOnSimObject(Enum DefineID, uint ObjectID, SIMCONNECT_DATA_SET_FLAG Flags, object pDataSet);

If you compare this signature with the one in the SimConnect SDK Documentation, you will see that the parameters ArrayCount and cbUnitSize are not found. I am not 100% sure, but I think that this has to do with the fact that in the Managed Code version, TAGGED data is not supported. At least, you can read this somewhere in the section Notes On .NET Client Programming.

Partial data return (SIMCONNECT_DATA_REQUEST_FLAG_TAGGED) is unsupported. For a description of this flag see the SimConnect_AddToDataDefinition function.

Let’s have a closer look at the parameters for SetDataOnSimObject:

  • DefineID: This is our earlier ID that we linked with our variable.
  • ObjectID: This is always 0 in my case. This tells SimConnect which “object” I’m controlling. 0 means that I’m controlling the plane I’m flying, and this will always be the case when I want to connect with my cockpit hardware.
  • Flags: We can choose between SIMCONNECT_DATA_SET_FLAG.DEFAULT and SIMCONNECT_DATA_SET_FLAG.TAGGED. As I don’t think TAGGED is supported (see above comment), I have not done any efforts to study this option. Hence I always use DEFAULT.
  • pDataSet: This is the data to be sent. Be careful that you use the same data type as the one you registered with the call to RegisterDataDefineStruct (in my implementation I’m using some dynamic casting).

Be aware that not all variables can be set. If you try to set a variable that is not settable, you will get a call to SimConnect_OnRecvException with some error.

  1. Getting a variable value

Finally we want to get the value of a variable. To do that, we will tell SimConnect that we are interested in the value of a variable. We will formalize this interest in a “Request” and give it a “RequestID”. SimConnect will then send back the value when it is available, and will identify this with our earlier provided RequestID. This means that there is no synchronous call to SimConnect to get the value of a variable, but only an asynchronous way of working.

Telling SimConnect that you are interested in a value is done by a call to the method RequestDataOnSimObject.

_oSimConnect.RequestDataOnSimObject(
    (SIMCONNECT_REQUEST_ID)v.uRequestID,
    (SIMCONNECT_DEFINITION_ID)v.uDefineID,
    0,
    SIMCONNECT_PERIOD.SIM_FRAME,
    SIMCONNECT_DATA_REQUEST_FLAG.CHANGED,
    0, 0, 0);

In the SimConnect SDK Documentation you can find the explanation for SimConnect_RequestDataOnSimObject.

The signature of RequestDataOnSimObject looks like this:

public void RequestDataOnSimObject(Enum RequestID, Enum DefineID, uint ObjectID, SIMCONNECT_PERIOD Period, SIMCONNECT_DATA_REQUEST_FLAG Flags, uint origin, uint interval, uint limit);

The principle is pretty simple. You tell SimConnect that you request the value referenced by DefineID, and you give this request a number RequestID. Once SimConnect has a value, it will call your EventHandler SimConnect_OnRecvSimobjectData where you can identify the request with the earlier provided RequestID.

RequestDataOnSimObject takes some extra parameters:

  • ObjectID: This is always 0 in my case. This tells SimConnect which “object” I’m controlling. 0 means that I’m controlling the plane I’m flying, and this will always be the case when I want to connect with my cockpit hardware.
  • Period: This tells SimConnect how many times we want the value. Possibilities are NEVER, ONCE, VISUAL_FRAME, SIM_FRAME and SECOND.
  • Flags: This can be DEFAULT, CHANGED and TAGGED.
  • origin, interval, limit: I suggest you look at the SimConnect SDK Documentation for this. In my implementation these don’t make much sense.

I’m using Period = SIM_FRAME and Flags = CHANGED. This means that SimConnect sends me the variable value every frame (for example 60 times per second). That is a lot of data to digest, especially if you are doing that with a few 100 variables. But here the Flags = CHANGED come to the rescue, because this means that I will only get an update when the value has changed. But if the value changes, it will be sent within one frame, which is pretty fast (fast enough for cockpit hardware).

The EventHandler that receives the value is shown below.

private void SimConnect_OnRecvSimobjectData(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA data)
{
    if (_oSimConnect != null)
    {
        VarData vInList = Vars.Find(x => x.cType == 'A' && x.uRequestID == data.dwRequestID);
        if (vInList != null)
        {
            vInList.oValue = data.dwData[0];
            LogResult?.Invoke(this, $"{vInList} value = {vInList.oValue}");
        }
    }
}

The Managed Code wrapper will make sure that the OnRecvSimobjectData EventHandler is provided with a structure SIMCONNECT_RECV_SIMOBJECT_DATA. There are a lot of members in that structure, but the first one that interests me is the RequestID. With this RequestID, I’m looking for the A-type variable in my List. If that variable is found, I update the value in the VarData object and send the result to my GUI. The fact that the value is stored in the VarData allows other processes to read from it in a synchronous way, but just be careful with locking in multithreaded approaches (not in scope to go further in detail here).

Remark: In my implementation, OnRecvSimobjectData will only be called for A-Type variables, so I don’t have to make the check x.cType == 'A', but this is to guarantee that my code is future proof.

If we are not interested anymore in the value of the variable, we can tell SimConnect to stop sending us updates. The safest way (to avoid an error) is to first stop the request with a call to RequestDataOnSimObject, but with the Period = NEVER. After that, we can call ClearDataDefinition, which simply removes the definition (and probably clears some memory as well).

_oSimConnect.RequestDataOnSimObject(
    (SIMCONNECT_REQUEST_ID)vInList.uRequestID,
    (SIMCONNECT_DEFINITION_ID)vInList.uDefineID,
    0,
    SIMCONNECT_PERIOD.NEVER,
    SIMCONNECT_DATA_REQUEST_FLAG.DEFAULT,
    0, 0, 0);
_oSimConnect.ClearDataDefinition((SIMCONNECT_DEFINITION_ID)vInList.uDefineID);

6. Summary

Let’s summarize the long explanation above.

In all cases, we need to call AddToDataDefinition providing a unique ID, and then we need to use RegisterDataDefineStruct to register the type of the variable (or struct).

v.SetID(VarData.AUTO_ID, VarData.AUTO_ID);
_oSimConnect.AddToDataDefinition(
    (SIMCONNECT_DEFINITION_ID)v.uDefineID,
    v.sName,
    v.sUnit,
    v.scDataType,
    0.0f,
    SimConnect.SIMCONNECT_UNUSED);

switch (v.scDataType)
{
    case SIMCONNECT_DATATYPE.INT32:
        _oSimConnect.RegisterDataDefineStruct<Int32>((SIMCONNECT_DEFINITION_ID)v.uDefineID);
        break;
    case SIMCONNECT_DATATYPE.INT64:
        _oSimConnect.RegisterDataDefineStruct<Int64>((SIMCONNECT_DEFINITION_ID)v.uDefineID);
        break;
    case SIMCONNECT_DATATYPE.FLOAT32:
        _oSimConnect.RegisterDataDefineStruct<float>((SIMCONNECT_DEFINITION_ID)v.uDefineID);
        break;
    case SIMCONNECT_DATATYPE.FLOAT64:
        _oSimConnect.RegisterDataDefineStruct<Double>((SIMCONNECT_DEFINITION_ID)v.uDefineID);
        break;
    case SIMCONNECT_DATATYPE.STRING256:
        _oSimConnect.RegisterDataDefineStruct<String256>((SIMCONNECT_DEFINITION_ID)v.uDefineID);
        break;
}

Then it depends on what we want to do.

  • If we want to write to a variable, we call SetDataOnSimObject
_oSimConnect.SetDataOnSimObject(
    (SIMCONNECT_DEFINITION_ID)vInList.uDefineID,
    0,
    SIMCONNECT_DATA_SET_FLAG.DEFAULT,
    vInList.oValue);
  • If we want to read from a variable, we first call RequestDataOnSimObject to tell SimConnect we want to get the value, and we use the SimConnect_OnRecvSimobjectData EventHandler to receive the data.
_oSimConnect.RequestDataOnSimObject(
    (SIMCONNECT_REQUEST_ID)v.uRequestID,
    (SIMCONNECT_DEFINITION_ID)v.uDefineID,
    0,
    SIMCONNECT_PERIOD.SIM_FRAME,
    SIMCONNECT_DATA_REQUEST_FLAG.CHANGED,
    0, 0, 0);

...

private void SimConnect_OnRecvSimobjectData(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA data)
{
    if (_oSimConnect != null)
    {
        VarData vInList = Vars.Find(x => x.cType == 'A' && x.uRequestID == data.dwRequestID);
        if (vInList != null)
        {
            vInList.oValue = data.dwData[0];
            LogResult?.Invoke(this, $"{vInList} value = {vInList.oValue}");
        }
    }
}

Finally, when we want to get rid of being sent values of a variable, we can get rid of it

_oSimConnect.RequestDataOnSimObject(
    (SIMCONNECT_REQUEST_ID)vInList.uRequestID,
    (SIMCONNECT_DEFINITION_ID)vInList.uDefineID,
    0,
    SIMCONNECT_PERIOD.NEVER,
    SIMCONNECT_DATA_REQUEST_FLAG.DEFAULT,
    0, 0, 0);
_oSimConnect.ClearDataDefinition((SIMCONNECT_DEFINITION_ID)vInList.uDefineID);

Goto Chapter 7: Making our own WASM module

3 Likes