SimConnect + WASM combined project using VS2019

Introduction

I made my own implementation of a SimConnect client written in C# in combination with a WASM module written in C++ using Visual Studio 2019. It took me several weeks to understand the concepts of SimConnect and WASM, and find the necessary information and documentation to be able to build my project. This tutorial will describe all the different steps in as much detail as possible.

EDIT 05MAY2022: In the meantime, I have added a new post on this forum: Tool to connect Serial USB devices to MSFS2020 via SimConnect/WASM using VS2022 - Community Support / Home Cockpit Builders - Microsoft Flight Simulator Forums. This new post makes use of all the technologies explained below.

The tool allows the following:

  • Connect/Disconnect from SimConnect
  • Add and Remove any possible SimVar, LVar or Event (SimConnect Event or Custom Event)
  • Automatic update when the value of a variable changes
  • Setting the values of variables
  • Feedback on SimConnect Exceptions

I created this project to be able to connect my proprietary cockpit hardware with MSFS. This limits the scope significantly, but it is a good starting point to explain some of the concepts. There are a lot of powerful tools available such as MobiFlight, FSUIPC, 
, but I can’t use them because my cockpit hardware is not based on Arduino, but on a PIC-Microcontroller using my own firmware.

I’m not a professional programmer, but have been writing software my whole life, because I like it. Although my implementation seems to work quite well, there might still be things that work “by accident”. For this reason, do not hesitate to give feedback and advice, and comment where necessary. We can all learn from it.

Next to several people that have given me answers in this forum, I want to specially thank @Dragonlaird and @MobiFlight, because their contributions were the foundation of my own implementation. @Dragonlaird has a very good tutorial here, and you can find all the @MobiFlight sourcecode on GitHub here.

I have never worked with GitHub before, but I think I managed to create a repository with my implementation, which you can of course freely download and use.
HansBilliet/SimConnectWasmHUB: SimConnect and WASM tryout (github.com)

I will divide my explanation in several chapters below in this post.

Goto Chapter 1: SimConnect and WASM - what is this?

18 Likes

Nice work. I am thinking about jumping in and checking the stuff out. My main interest is getting a window VR. I would like to display maps or other information such as frame rate and other telemetry. I know there are some free and paid options, but I would like to try my hand at it.

Anyone have any information on that to get me started?

Chapter 1: SimConnect and WASM - what is this?

If I want my cockpit hardware to talk to MSFS2020, I need an interface to do that. That interface is called “SimConnect”. It is definitely not a bad idea to have a look at the SimConnect SDK Documentation. SimConnect is not automatically installed, but you can easily do this with these installation instructions. There are a lot of interesting samples here, and some more explanation of how to use SimConnect with C#.

SimConnect works according the client/server concept, where MSFS is the server and your application is the client. The SimConnect client “connects” with the SimConnect server. The client can be running on the same computer where MSFS2020 is running, but it can also be running on a different computer in the network (a lot of cockpit builders are distributing the hardware between different computers). Once connected, the SimConnect client can then use all the functions that are described in the API (Application Programming Interface). With these functions you can then read and write to all kinds of variables or trigger events.

But unfortunately not all variables or events can be controlled through SimConnect only. Some special variables like LVars and HVars (don’t bother too much about the names now) need some extra piece of software. This extra piece of software is based on a WASM module (WASM stands for Web Assembly Module). Documentation about the Web Assembly can be found here.

The below is a very simple representation of how all the pieces of the puzzle fit together.

That is now why my implementation includes 2 separate projects:

  • A SimConnect Client based on a Windows Form which also acts as the GUI - this is written in C#
  • A WASM module that works together with the SimConnect Client to allow accessing LVars and HVars - this is written in C++

Off topic, but my PIC-Controlled hardware is written in C, so I am really using all the C-languages. But that should not be a surprise, because I’m Belgium that loved the sound of C back in the 80’s.

Goto Chapter 2: Setting up the environment

6 Likes

Chapter 2: Setting up the environment

In Visual Studio you create a solution that contains 1 or more projects. As I explained in Chapter 1, I need 2 projects.

image

The below steps will explain some specifics of each project.

  1. SimConnectWasmHUB

This is the SimConnect Client application written in C#. I based it on a "Windows Form App (.NET Framework) and used .NET Framework 4.7.2.

image

If you want to make use of the SimConnect SDK, you have to include the following namespace in your sourcefile:

using Microsoft.FlightSimulator.SimConnect;

This is the C# wrapper of the SimConnect dll. To make the using statement work, you first need to copy the Microsoft.FlightSimulator.SimConnect.dll from C:\MSFS SDK\SimConnect SDK\lib\managed to your project (I copy it where my source files of my projects are located). Be aware that [C:\MSFS SDK] is where my SDK is installed, and can be different on your computer.

Once this file is copied, you have to include it as a reference in your project.

image

In the next screen you go to “Browse” and use the “Browse
” button to locate the dll file you just copied. Press ok to add the reference. Now the above “using” statement should work.

But the C# wrapper makes use of the real SimConnect dll. As instructed in the SDK documentation, the best way is to make sure that your target directory includes a copy of “SimConnect.dll”. A way to do that automatically is to add it as a file in your project, and make sure that it is always copied to your target directly.

Make sure you search for “Executable Files (.exe,.dll,*.ocx)”, and select the file from “C:\MSFS SDK\SimConnect SDK\lib\SimConnect.dll”. After that, select the SimConnect.dll file in your project, and in its properties change to “Copy if newer”.

image

SimConnect is only working with 64-bit targets. If your target platform shows “Any CPU”, you can change it with the Configuration Manager of your solution by creating a new “Active solution platform” and make it “x64”.

Now your project is ready to use SimConnect.

  1. WASM module

You can create a separate solution for your WASM project, but I added it in the same. You can easily do that by right clicking on your existing solution and select “Add / New project
”.

If you have installed the SimConnect SDK, then you will be able to select some special project types made for Microsoft Flight Simulator. Select the MSFS WASM Module.

image

Once the WASM project is added, then you have to makes sure that it is build when you “Rebuild Solution”. You can do that in the Configuration Manager.

First test build

Now it’s a good time to do a first test build (menu Build / Rebuild Solution). If you get the below result, you’re good!

========== Rebuild All: 2 succeeded, 0 failed, 0 skipped ==========

Don’t worry if you get a lot of Warnings and Messages in the “Error List”. I have the feeling that IntelliSense struggles a lot with the WASM project - I’m even getting 3066 Errors, although my build is successful. But I could get rid of all of them by selecting “Build Only”, which is ignoring the IntelliSense errors.

image

Goto Chapter 3: Connection with SimConnect

8 Likes

Hello @ElatedLeopard71,

My interest is in connecting Cockpit hardware with MSFS2020, but the tool I’m am presenting here is pretty generic and allows to control any SimVar, LVar or KVar, and can easily be expanded.

The tool itself allows to experiment with all kind of variables, and see what the result is. I also will have to do this when I want to connect my hardware. I first will need to find out which variables I need and how I can control them.

2 Likes

Very cool and impressive work. Major Creds !!

2 Likes

Wonderful project that I will follow with great interest.
You study in this post the two main things that I am seeing currently for my own project. You have a professional approach which is very interresting.
Thanks to you and happy New year!

2 Likes

Chapter 3: Connection with SimConnect

Now we have the framework of our VS2019 Solution ready, we can start with the real work. First we start with some basics on how to connect and disconnect with SimConnect. We will discuss the WASM module in a later chapter.

I am not going to dig into the C++ approach, because I am using the C# version (managed code) which simplifies a lot. But just be aware the all the documentation is written with C++ in mind. You will see that C# uses a slightly different syntax.

The below shows the difference between a C++ and C# implementation for opening a SimConnect client session and using some function.

C++ implementation:

// SimConnect HANDLE
HANDLE hSimConnect = NULL;  

// SimConnect result
HRESULT hr;

// Open the SimConnect client session
hr = SimConnect_Open(&hSimConnect, "Your Application Name", hWnd, WM_USER_SIMCONNECT, 0, 0);
if (hr != S_OK)
    ... error processing

// Use some SimConnect function
hr = SimConnect_AddToDataDefinition(
    hSimConnect,
    DEFINITION_1,
    "Kohlsman setting hg",
    "inHg");
if (hr != S_OK)
    ... error processing where "hr" can be used to identify the error

C# implementation:

// SimConnect object
private SimConnect _oSimConnect = null;

try
{
    // Open the SimConnect client session
    _oSimConnect = new SimConnect("SimConnectHub", hWnd, WM_USER_SIMCONNECT, null, 0);

    // Use some SimConnect function
    _oSimConnect.AddToDataDefinition(
        DEFINITION_1,
        "Kohlsman setting hg",
        "inHg",
        SIMCONNECT_DATATYPE.FLOAT64,
        0.0f,
        SimConnect.SIMCONNECT_UNUSED);
}
catch (COMException ex)
{
    ... error processing where "ex.Message" can be used to identify the error
}

There are 2 main differences:

  1. C++ makes use of a HANDLE that identifies the SimConnect client session, and is obtained from the SimConnect_Open call. It is used as first parameter in all further calls to SimConnect functions.
    C# makes use of a SimConnect object, in which the constructor takes care of the HANDLE internally (probably some private property). The constructor acts as a wrapper of the SimConnect_Open function. The SimConnect functions are members of the SimConnect object, omitting the prefix “SimConnect_” from the function name.
    Example: “hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, 
” becomese “_oSimConnect.AddToDataDefinition(DEFINITION_1, 
”.
  2. The error handling in C++ is using the return value HRESULT. If this value is not equal to S_OK, there is an issue. In C# this is translated in the raising of a COMException, that can be used within a try/catch block.

It is definitely worth reading the section Programming SimConnect Clients using Managed Code. It contains a lot more specifics on the C# wrapper. I’m not going to repeat them all in this post.

The SimConnect constructor has the following signature:

public SimConnect(string szName, IntPtr hWnd, uint UserEventWin32, WaitHandle hEventHandle, uint ConfigIndex);

An easy way to find these signatures is right clicking on the method (in this case “SimConnect”, as that is the constructor name), and choose “Peek Definition”.

image

The problem with SimConnect is the lack of documentation :frowning_face:. So sometimes you will have to do some “guesswork”. For example, I have no clue what the “WaitHandle” is doing as the SimConnect documentation doesn’t talk about this in the context of C#. But we seem not to need it for now, so we will use null which seems to work just fine. (if anybody has a clue, don’t hesitate to comment).

Two important parameters are:

  1. IntPtr hWnd
  2. uint UserEventWin32

It means that when opening a SimConnect session, we will need to provide a window handle of a form that has a WndProc (if I’m not mistaken, all forms have one). SimConnect will then interact with this form by sending messages with the Msg defined in UserEventWin32. You simple need to hook into the WinProc, and dispatch all SimConnect messages to your application.

If you are planning to build a SimConnect client as a DLL, then you probably don’t want to have a visible form. In that case, you will have to use a “MessagePump” (which is actually some kind of “hidden” form). I suggest you look at this post written by @dragonlaird who is exactly doing this.

But as my implementation is based on a Windows Form, I can simply use that handle and pass it to my SimConnect constructor. A good moment to do that is when the Form is “shown”. In VS2019 this is even extremely simple by including the “Shown Event” - just double click on the empty space next to “Shown” below, and it will automatically add “FormHUB_Shown” for you.

image

Let’s have a look in the “FormHUB.cs” file included in my project (I only explain the parts that are relevant - the order of the methods might also differ):

// file FormHUB.cs

public partial class FormHUB : Form
{
    SimConnectHUB _SimConnectHUB = new SimConnectHUB();

    private void FormHUB_Shown(object sender, EventArgs e)
    {
        _SimConnectHUB.SetHandle(this.Handle);
    }
   
    protected override void WndProc(ref Message m)
    {
        _SimConnectHUB?.HandleWndProc(ref m);

        base.WndProc(ref m);
    }

    ....
}

The above is pretty selfexplanatory:

  1. A new “SimConnectHUB” object is created, which is the SimConnect client implementation (file SimConnectHUB.cs)
  2. When the Form is shown, I pass its windows handle to SimConnectHUB
  3. In the Form, WndProc is intercepted, and calls the SimConnectHUB.HandleWndProc before calling its base.WndProc (remark: I could have checked for the specific SimConnect message here, but I do that in HandleWndProc - doesn’t make a lot of difference really).

In the SimConnectHUB object, you will find the implemenation to store the Form handle and deal with the WndProc call:

// file SimConnectHUB.cs

// User-defined win32 event
public const int WM_USER_SIMCONNECT = 0x0402;
// Window handle
private IntPtr _handle = new IntPtr(0);

// Keep Window Handle for connecting with SimConnect
public void SetHandle(IntPtr handle)
{
    _handle = handle;
}

// WndProc hook
public void HandleWndProc(ref Message m)
{
    if (m.Msg == WM_USER_SIMCONNECT)
    {
        ReceiveSimConnectMessage();
    }
}

public void ReceiveSimConnectMessage()
{
    try
    {
        _oSimConnect?.ReceiveMessage();
    }
    catch (Exception ex)
    {
        LogResult?.Invoke(this, $"ReceiveSimConnectMessage Error: {ex.Message}");
        Disconnect();
    }
}

In HandleWndProc, we check if SimConnect has something to say. If it does, then we call “ReceiveSimConnectMessage()” that will call the SimConnect “ReceiveMessage()” method. This is the method that triggers all the SimConnect magic.

I could have put the call to “ReceiveMessage()” directly in “HandleWndProc”, but decided not to do so in case I want to call “ReceiveMessage()” myself somewhere in the code (although, so far this was not needed).

Now that the preparation is done, we are ready to use the “Connect” and “Disconnect” methods that are called by the Connect and Disconnect buttons on the form.

The Connect method looks like this:

// file SimConnectHUB.cs

public void Connect()
{
    if (_bConnected)
    {
        LogResult?.Invoke(this, "Already connected");
        return;
    }

    try
    {
        _oSimConnect = new SimConnect("SimConnectHub", _handle, WM_USER_SIMCONNECT, null, 0);

        // Listen for connect and quit msgs
        _oSimConnect.OnRecvOpen += SimConnect_OnRecvOpen;
        _oSimConnect.OnRecvQuit += SimConnect_OnRecvQuit;

        // Listen for Exceptions
        _oSimConnect.OnRecvException += SimConnect_OnRecvException;

        // Listen for SimVar Data
        _oSimConnect.OnRecvSimobjectData += SimConnect_OnRecvSimobjectData;

        // Listen for ClientData
        _oSimConnect.OnRecvClientData += SimConnect_OnRecvClientData;

        LogResult?.Invoke(this, "Connected");
    }
    catch (COMException ex)
    {
        LogResult?.Invoke(this, $"Connect Error: {ex.Message}");
    }
}

The C# wrapper of SimConnect provides an easy way to intercept interactions by adding EventHandlers that are called when some specific SimConnect events are happening. In my implementation, I only provided the ones that are relevant, but if you use “Peek Definition” you will find 27 of them. Some event names are self-explanatory, but for other ones you might have to dig in to the documentation or google, or simply experiment.

The ones that I added are:

  • OnRecvOpen: This will be called by SimConnect when the session successfully opened, and will be used to initialize some additional items.
  • OnRecvQuit: If we close MSFS2020, then SimConnect will notify our client through this event.
  • OnRecvException: If SimConnect discovers some error, it will notify our client through this event.
  • OnRecvSimobjectData: This is to receive data from Simobjects (SimConnect variables)
  • OnRecvClientData: This is to receive data from Client Area Data which is going to be used to communicate with our WASM module.

The Disconnect method looks like this:

// file SimConnectHUB.cs

public void Disconnect()
{
    if (!_bConnected)
    {
        LogResult?.Invoke(this, "Already disconnected");
        return;
    }

    // Raise event to notify client we've disconnected and get rid of client
    _oSimConnect?.Dispose(); // May have already been disposed or not even been created, e.g. Disconnect called before Connect
    _oSimConnect = null;
    _bConnected = false;
    LogResult?.Invoke(this, "Disconnected");
}

“LogResult” is my own EventHandler that is used to send information back to a multiline TextBox in the form. Because my SimConnectHUB object has no clue at compile time where to send its result to, we deal with that using an EventHandler.

// file SimConnectHUB.cs

public event EventHandler<string> LogResult = null;

When the constructor of the form is called, this EventHandler gets initialized like this:

// file FormHUB.cs

public FormHUB()
{
    InitializeComponent();

    _SimConnectHUB.LogResult += OnAddResult;
}

private void OnAddResult(object sender, string sResult)
{
    if (textResult.Text != "")
        textResult.AppendText(Environment.NewLine);
    textResult.AppendText(sResult);
}

Now SimConnectHUB can Invoke the EventHandler whenever it has something to tell. I’m using the “Null-conditional operator” (the ‘?’-character) in the call, which guarantees that LogResult will only be used when not null (see here).

// file SimConnectHUB.cs

LogResult?.Invoke(this, "some text");

Et voila, we can connect and disconnect our SimConnect Client, and get some results in a multiline TextBox. Ready to start using SimConnect in the next chapter.

Goto Chapter 4: Variables

6 Likes

Without wanting to give compliments for the pleasure of giving them, I think that you are a very good teacher and that you know how to “popularize” your subject without drowning the reader in details.
I read DragonLaird’s posts on FSDeveloper since many years and and I must admit that he is as good as you.
Continue, I follow.

2 Likes

Probably OK in your code, but I assume the _bConnected is set to True upon connection?

You are right @TFEV1909, but I control the flag only in the EventHandlers “OnRecvOpen” and “OnRecvQuit”, because then you are sure that the connection or disconnection really succeeded. Especially for then “OnRecvQuit”, that can even happen “unexpectedly” when MFSF is shut down.

// file SimConnectHUB.cs

private void SimConnect_OnRecvOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data)
{
    Debug.WriteLine("SimConnect_OnRecvOpen"); 
            
    _bConnected = true;

    LogResult?.Invoke(this, $"- Application name: {data.szApplicationName}");
    LogResult?.Invoke(this, $"- Application Version {data.dwApplicationVersionMajor}.{data.dwApplicationVersionMinor} - build {data.dwApplicationBuildMajor}.{data.dwApplicationBuildMinor}");
    LogResult?.Invoke(this, $"- SimConnect  Version {data.dwSimConnectVersionMajor}.{data.dwSimConnectVersionMinor} - build {data.dwSimConnectBuildMajor}.{data.dwSimConnectBuildMinor}");

    InitializeClientDataAreas();
}

private void SimConnect_OnRecvQuit(SimConnect sender, SIMCONNECT_RECV data)
{
    Debug.WriteLine("SimConnect_OnRecvQuit");
    Disconnect();

    _bConnected = false;
}
1 Like

That makes sense. Your steps convinced me to start working on my on C# Simconnect application (potentially moving away from my current Python implementation). Thx a lot!

Chapter 4: Variables

One of the things that confused me most when I was starting with SimConnect are all the different types of variables. This is probably the most difficult chapter for me to explain.

@Umberto67 posted a nice summary of several variables in this post. At the time that I read this (about a month ago), I only understood half of it. But the good thing is that you can bookmark posts in this forum, and re-read them when you have acquired more knowledge. Thanks to a lot of reading, I think I start getting it. But I haven’t experimented with all the possible variable types yet, and might even never need to do so within the scope of my implementation. This means that there are definitely some bits and pieces that I might still be missing, so be a bit cautious with the explanation I’m giving below.

Bottom line is that we have “real variables” which you can give a value and/or you can read the value from (example: “KOHLSMAN SETTING HG”), and some are called “events” which you only trigger and do not necessarily need a value (example: “A32NX.FCU_HDG_INC”).

In the below explanation, I will call them all “variables”, which is including “real variables” and “events”.

We can make a distinction between 2 types of variables:

  1. Variables directly controlled via SimConnect
    I call these variables “native MSFS2020 variables”, because they are found within the simulator. You can find a detailed explanation in the SDK Documentation, where these are called “Simulation Variables” and Events.
    Example of such variable: AUTOPILOT ALTITUDE LOCK VAR.
    The main characteristic of these variables is that we can control them directly with SimConnect.

  2. Variables controlled through a WASM module
    I call these variables “custom variables”, because they are in a lot of (all of the?) cases very specific for a certain object in MSFS2020, example an add-on airplane.
    Example of such variable: A32NX_EFIS_L_OPTION. This is only available for the FlyByWire A32NX, which is a free A320 add-on of very high quality that you can find here. I’m going to use this add-on regularly in this tutorial.
    The main characteristic of these variables is that we can only control them through a WASM module.

If we are going to deal with WASM (see in one of the next chapters), we can use a function called execute_calculator_code. The nice thing is that with this function, you can access all the types of variables, whether they are “native MSFS2020 variables” or “custom variables”. But for this function to know which type of variable we are dealing with, the variables are prefixed with a character and a ‘:’. You can find all these prefixes in the section Variable Types in this part of the SimConnect SDK Documentation. This immediately shows how many different variables do exists (and I only know a few of them so far).

I will use the same type of prefixes in my implementation. At the time of writing this tutorial, the following variable types are used:

  • A: - These are the “native MSFS2020 variables”, which in the FBW documentation are also called “SIMCONNECT VAR”.
  • L: - These are the “custom variables” or also called LVars, which in the FBW documentation are also called “Custom LVAR”.
  • K: - These are EVENTS, and could be “native” or “custom”. With SimConnect, the rule is that if the variable name doesn’t contain a dot ‘.’, it is a “native” one and can be controlled directly via SimConnect. If the name contains a “.”, then it is a custom event and needs to be controlled through WASM. In the FBW documentation these are also called “SIMCONNECT EVENT” or “Custom EVENT”.

Remark: In the FBW documentation, SIMCONNECT VAR or SIMCONNECT EVENT is sometimes written as MSFS VAR or MSFS EVENT - at least, I think that these are the same. If my interpretation is wrong, please don’t hesitate to comment.

The examples we are going to use in my implementation are the following:

  • A:AUTOPILOT ALTITUDE LOCK VAR (native MSFS variable)
  • A:KOHLSMAN SETTING HG (native MSFS variable)
  • A:LIGHT POTENTIOMETER (native MSFS variable)
  • L:A32NX_EFIS_L_OPTION (custom FBW A32NX variable)
  • L:A32NX_EFIS_R_OPTION (custom FBW A32NX variable)
  • K:FUELSYSTEM_PUMP_TOGGLE (native MSFS event - there is no “.” in the name)
  • K:A32NX.FCU_HDG_INC (custom FBW A32NX event - there is a “.” in the name)

One of the challenging things is finding the variable you need to control a certain switch, or read a certain indication in an airplane. Because that’s what you need if you want to interface with your cockpit hardware. I have no common method for this, because it will heavily depend on the airplane you are dealing with. But I will explain some methods that I used with FBW A32NX.

Because FBW A32NX is a custom add on, you won’t find any information in MSFS2020 or SimConnect SDK. You need to look into the documentation of the creators of the add-on. Luckily, FBW A32NX is free and open source, which means that all information is available on the web.

Let’s give a few examples.

Example 1:

I want to read the value of altitude on the FCU (Flight Control Unit) of the A32NX.
image
In the FBW A32NX documentation there is a section for the FCU panel. If you scroll down to the “ALT knob”, you find that you can use the variable “AUTOPILOT ALTITUDE LOCK VAR:3”. This is an “MSFS VAR”, which in my terminology means a “native variable”. You will indeed also find this variable in the SDK Documentation here, where you can read that the value is given in “feet”, and that you can both read from and write to it.

In my implementation, this variable will be entered as “A:AUTOPILOT ALTITUDE LOCK VAR:3,feet,FLOAT64”. Don’t worry too much about the syntax I’m using, and the word FLOAT64. We will see that in some later chapters.

Example 2:

I want to control the 5 buttons on the EFIS (Electronic Flight Instrument System) panel of the A32NX.
image
In the FBW A32NX documentation there is a section for the EFIS control panel. If you scroll down to the “ND Filter”, you find that you can use the variable “A32NX_EFIS_L_OPTION” and “A32NX_EFIS_R_OPTION”. These are “Custom LVAR”, which in my terminology means “custom variables”. You won’t find these in the SDK Documentation. The variable is “R/W” and can take 6 values: 0=none, 1=CSTR, 2=VOR, 3=WPT, 4=NDB, 5=ARPT.

In my implementation, this variable will be entered as “L:A32NX_EFIS_L_OPTION,enum” and “L:A32NX_EFIS_R_OPTION,enum”.

Some more ways to find variables

1. The behaviors window

If you have enabled “Developer Mode” in MSFS2020 (which you probably have to install the SimConnect SDK), then you can also use the “Behaviors” window:

image

This is (at least for me :laughing:) a very complex tool, but it can help revealing some last pieces of the puzzle in finding the correct variables. Hover over a button until it gets the blue focus:

image

Now press “CTRL-G” while the behaviors window is open. The behaviors window will now show a lot of information for the control you selected. I have no clue about most of the information presented here, but if you open a few of the entries, you might find some hints on what variables to use.

Edit 06DEC2022: For some reason, the CTRL-G trick seems not to work anymore. I still haven’t found a way to get it working again. Maybe this feature is depreciated (but I wouldn’t know why Asobo would do that). Or I must be doing something wrong after several MSFS updated? If somebody knows, please send me a PM. Thanks!

image

The above shows again the usage of variable “L:A32NX_EFIS_L_OPTION, enum”, which is the one we showed before. The code is written using RPN (Reverse Polish Notation) that is explained in the SDK Documentation. I’m not going in much more detail here, because it’s not really in the scope of this tutorial.

2. Some interesting websites

We are obviously not the only ones that are looking for variables. In an attempt to avoid to reinvent the wheel, some flightsim enthousiasts have created a website Hubhop that is consolidating the research of many people in finding the right variables. The information found on this website might be shown as “presets” which can be used by other tools like MobiFlight. But it does reveal some useful information as you can see in the below screenshot, where we again see the usage of the variable “L:A32NX_EFIS_L_OPTION” (and if you understand the RPN code, you will see that the preset is toggling the WPT button).

Just be careful with the information found on this website. Everybody can enter information in it, so it is good practice to double check the information. But it is still a great source!

Conclusion on variables

As you can see, it is not easy to find the correct variables. There is no simple method that will work in all cases, but will depend on what you are looking for, and whether you are dealing with an add-on or native MSFS2020. But with the tool that we are making in this tutorial, you will at least have the possibility to experiment by trying out several variables and see the results, until you find the correct ones that you can then use for your own project.

Goto Chapter 5: Variable housekeeping using object VarData

5 Likes

So with help of this topic I managed to build my first C# SimConnect application. I also included a basic event setter, using this site: (C#) I can read! Can I write? | FSDeveloper.

Great stuff!

Translating the data received from Simconnect back into the Struct was the most difficult step, as I’m not an experienced C# developer yet (and probably never :rofl: ).

1 Like

Chapter 5: Variable housekeeping using object VarData

This chapter is a bit “off topic”, but to avoid that people get completely lost in my implementation, I will take some time to explain.

In my object SimConnectHUB, variables are maintained in a “List” with objects of type “VarData”.

// List of registered variables
private List<VarData> Vars = new List<VarData>();

The details of object VarData can be found in the file “VarData.cs”. This object takes care of a few things:

  • Storage of all kinds of values that are needed for the variable (ID’s, name, type, etc
)
  • A helper method “SetID” to assign ID’s, and eventually create them automatically
  • A set of methods to implement the “IEquatable” interface, which allows comparing 2 VarData objects (needed to check if a VarData item already exists in the List, to avoid duplicates)
  • A method “ParseVarString” that parses a string provided by the user, extracting all the values and report if that was successful

The VarData method public ParseResult ParseVarString(string sVar) takes a string with a specific format, which depends on the variable type. The method validates the string sVar, and extracts all the neccessary values. If successful, it will return the enum “ParseResult.Ok”. If not, it will return an error that gives a hint on what is wrong.

There are currently 3 variable types supported:

  1. Type "A"
    These are “native MSFS2020 variables”
    Format: “A:[Variable name],[unit],[DataType]”
    Example: “A:AUTOPILOT ALTITUDE LOCK VAR:3,feet,FLOAT64”

  2. Type "L"
    These are “custom variables”
    Format: "L:[Variable name],[unit]
    Example: “L:A32NX_EFIS_L_OPTION,enum”

  3. Type "K"
    These are events.
    Format: "K:[Event name] (if it contains a “.”, it is a custom event)
    Example of native event: “K:FUELSYSTEM_PUMP_TOGGLE”
    Example of custom event: “K:A32NX.FCU_HDG_INC”

The object SimConnectHUB implements 3 methods that make use of the VarData object and the List.

  • public bool AddVariable(string sVar) - called when pressing the button “Add Variable”
  • public bool RemoveVariable(string sVar) - called when pressing the button “Remove Variable”
  • public bool SetVariable(string sVar, string sValue) - called when pressing the button “Set Value”

Each method will start with doing some “housekeeping” like checking if we are connected, checking if the user input is valid (not empty and parsing is successful) and checking if the variable does or does not exist already. If it passes all the tests, the real SimConnect work can start which will be explained in the next chapters.

I will not spend more time in explaining the implementation of VarData, because this tutorial is about SimConnect. But I think that the implementation is pretty self-explanatory. If you would have any questions, don’t hesitate to ask of course.

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

2 Likes

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

Hello Hans,

Apologies for the noob question. I am trying to send commands to MSFS through SimConnect. What is the difference between the Simulation Event ID and the Simulation Variables? Which should I use? Thank you.

Hello @AthenaGrey1,

I will control Events in a later chapter, but in Chapter4: Variables I also referred to a good explanation given by @Umberto67 that you can find in this post.

Sometimes you can use a variable or an event to achieve the same goal. Let’s take an example.

If we look at the FCU Panel documentation of FlyByWire, scrolling down to the “ALT knob”, we see the following:

The first one is an “MSFS Variable” and can be controlled directly with SimConnect. In my explanation above, I call this “native MSFS variables”, or “A-Type”. If you use my application, you can add the variable as “A:AUTOPILOT ALTITUDE LOCK VAR:3,feet,FLOAT64”, and then you can read from it, and even write to it. Makes sense, because if we look in the SimConnect SDK Documentation and search for the variable “AUTOPILOT ALTITUDE LOCK VAR” here, we see the green tickmark on the right indicating that this is a writeable variable (in fact, ALL variables are readable, some of them are writeable).

But the FlyByWire documentation also gives us 2 "Custom EVENT"s that can be used to Increase and Decrease the ALT. In my explanation above, I call this “custom variables”, which in case of Events are “K-Type”. If you use my application, you can add them as “K:A32NX.FCU_ALT_INC” and “K:A32NX.FCU_ALT_DEC”. If you then use the button “Set Value” in my application (the value is even irrelevant) you will see that the ALT is increasing and decreasing.

So when do you use the variable, and when do you use the event? That really depends on the implementation. In case of ALT, using the events has some advantages. In the A320, you can only increase or decrease with steps of 100 or 1000 (depending on the selection you made on the FCU). But if you use the variable, you seem even to be able to give a value like “12345”, which is normally not possible in the A320. I think there is even no guarantee that this couldn’t cause any unpredictable effects. So if you use the variable directly, you might need to build some logic yourself to avoid giving wrong values. So in my opinion, it’s safer to use the event in this case.

Probably a bit off-topic here, but I do have a reason to use the variable directly. If you use the INC/DEC Events with a rotary encoder in your cockpit hardware, there might be some “lag” when you turn the knob fast. Reason is that for each tick, you have to send an INC/DEC event. In my cockpit hardware (in the past, using JeeHell A320 FMGS with FSX), I used a different approach in which my encoder is driving the 7-Segment display directly, which means that it goes very fast and shows all the ticks. This gives a very reactive feel. And I only send the value to the simulator every 200 msec controlling the variable directly, which reduces the communication a lot. Although, you still have to build some extra “synchronization logic” to make sure that, if the simulation changes the ALT value itself, that this is also displayed correctly (without turning the encoder). I might explain this method later in the Cockpit Builder section on this forum.

3 Likes

Thank you very much for your helpful and detailed explanation.

Chapter 7: Making our own WASM module

If we want to control “custom variables”, we will need to do that through a WASM module. In Chapter 1: SimConnect and WASM - what is this? I already briefly touched on what a WASM module is, and in Chapter 2: Setting up the environment I showed how to prepare your VS2019 solution if you want to develop your own WASM module from scratch.

You can not use C# for WASM. You have to use C/C++ instead. So it might be a bit confusing if you have been dealing with the above chapters, like you have been speaking Italian, and now you have to switch to Spanish :laughing:.

Build our first WASM module

Unfortunately, the few lines of code that Microsoft includes in the WASM template are not very helpful.

// Test.cpp

#include <stdio.h>
#include "Test.h"

extern "C" MODULE_EXPORT void test(void)
{
	// TODO
}

I would at least have expected that the template would include something more useful code such as a framework for the initialization and de-initialization.

The most basic WASM module could look like this - let’s call the project “Test_WASM”:

// Test_WASM.cpp

#include <MSFS/MSFS.h>
#include <MSFS/MSFS_WindowsTypes.h>

#include <SimConnect.h>

#include <MSFS/Legacy/gauges.h>

#include "Test_WASM.h"

const char* WASM_Name = "Test_WASM";
const char* WASM_Version = "00.01";

extern "C" MSFS_CALLBACK void module_init(void)
{
	HRESULT hr;

	fprintf(stderr, "%s: Initializing WASM version [%s]", WASM_Name, WASM_Version);

	// ... do some initialization

	fprintf(stderr, "%s: Initialization completed", WASM_Name);
}

extern "C" MSFS_CALLBACK void module_deinit(void)
{
	fprintf(stderr, "%s: De-initializing WASM version [%s]", WASM_Name, WASM_Version);

	// ... do some de-initialization

	fprintf(stderr, "%s: De-initialization completed", WASM_Name);
}

Let’s first look at the includes:

  • MSFS/MSFS.h and MSFS/MSFS_WindowsTypes.h are always required.
  • MSFS/Legacy/gauges.h is required if you want to use the Gauge API. In my opinion, it doesn’t make much sense to create a WASM Module without using the Gauge API, because without that your WASM Module can’t do anything useful.
  • SimConnect.h is needed when you want your WASM module to use SimConnect. Also this seems pretty obvious, because as explained in earlier chapters you need SimConnect to be able to interact with the WASM Module.
  • Test_WASM.h is added by the template automatically

Next to that we have 2 important functions:

  • module_init which is called when the WASM module is starting. Here we can do all kinds of initializations to bring our WASM module to life.
  • module_deinit which is called when the WASM module stops. Here we can do some de-initialization and cleanup.

Remark: To get some information on what our WASM Module is doing, I’m using the fprintf command and write to “stderr”. This will be shown in the MSFS2020 Console window as error messages. I could also write to “stdout”, which would show it as warning messages. But if I use “stdout”, it seems that after a newline (“\n”) the next lines aren’t shown anymore. It’s like I can only print one line to “stdout”, and that’s it. I might be missing something, but it’s maybe not a coincidence that @MobiFlight in their WASM Module also uses “stderr” as output. I already posted a question on this in this forum, but no answers yet. If somebody has a clue, please let me know in this post.

We can build the above WASM module in a separate project. If you have used the instructions in Chapter 2: Setting up the environment, then you should find your WASM module in [Project folder]\Test_WASM\MSFS\Debug\Test_WASM.wasm (or in the Release folder if you build a release version).

Use our first WASM module

Our WASM Module can be installed as a Gauge or as a Stand-Alone Module. I have no clue how to install it as a Gauge, but I know how to install it as a Stand-Alone Module. For this, you need to copy it to the MSFS2020 Community folder. You have to use a specific folder structure and also add 2 additional files “layout.json” and “manifest.json”.

Let’s first find the Community folder. It all depends whether you have installed MSFS2020 from the store, or via Steam. But there is a very easy way to find this. Launch MSFS2020, and if Developer Mode is enabled, you can use the menu on top and go to “Tools/Virtual File System”.

image

From the next window you can directly “Open Community Folder”.

image

Now create a folder for you WASM module, and in that folder create a subfolder “modules”.

image

Copy the earlier built file “Test_WASM.wasm” in the “modules” folder.

Next to that, we need 2 json files in the “Test_WASM” folder (not in the “modules” subfolder!).

image

The content of the json files is below:

layout.json

{
  "content": [
    {
      "path": "modules/Test_WASM.wasm",
      "size": 28171,
      "date": 132857136167661584
    }
  ]
}

manifest.json

{
  "dependencies": [],
  "content_type": "MISC",
  "title": "Test_WASM",
  "manufacturer": "",
  "creator": "HABI",
  "package_version": "0.1.00",
  "minimum_game_version": "1.19.8",
  "release_notes": {
    "neutral": {
      "LastUpdate": "",
      "OlderHistory": ""
    }
  }
}

Make sure that the “path” and “size” in the layout.json and the “title” in the manifest.json is using the correct info about your WASM Module. I have no more knowledge about the “manifest.json”, but the one shown here works fine. For the “layout.json”, make sure that the size of your WASM module is correctly set each time you update the WASM Module. You can easily find this size by right clicking on the Test_WASM.wasm file, and select properties. There you will find the size that you have to type over in the “layout.json” file.

image

There is also a handy tool MSFSLayoutGenerator which allows you to drag the “layout.json” file on it, and it will automatically update the size and even the date. When writing this tutorial, I could download the executable from here.

When all files are copied and json files are updated, we can start MSFS2020 (time to cross some fingers :crossed_fingers:). If you have done everything correctly, then you should see the below messages in the Console Window in MSFS2020 (in Developper mode, menu “Windows/Console”).

Now we are able to develop our own WASM module, we can start with writing its body to deal with our “custom variables” and communicate with our client through SimConnect.

Help needed! How can we restart a WASM Module without restarting MSFS2020?

The main issue with developing a WASM Module if you put them in the Community folder (Stand-Alone Module) is that you have to restart MFSF2020 completely after each change. Knowing that restarting the sim takes several minutes already makes it clear that developing a WASM Module is not an easy and smooth process. There is a post on this forum from @Voss1917 to ask if there is a way to restart a WASM Module without restarting MSFS2020, but so far none of the offered solutions seems to work.

It seems that installing the WASM Module as a Gauge should do the job, because then you are not using it as a Stand-Alone Module, but as part of an airplane. You can then restart your airplane, which should also restart the WASM Module. But I have no clue how to do that.

I hope that some clever people can come up with some answer.

EDIT: @RoystonSidhe came with the solution!!! You find his explanation here. This is a real time-saver! Reloading the WASM Module only takes about 15 seconds, which is a huge improvement compared to having to restart the sim every time. THANKS A MILLION RoystonSidhe!

Goto Chapter 8: Communicating with the WASM Module through Client Data Areas

4 Likes