LVAR read and write through custom SimConnect events

Hi all,

Following my previous topic (Demo: LVAR write access for any aircraft control) that introduced how one can easily write to any LVAR, and trigger any key event that is not visible by default as a SimConnect event, I here complete the story by demonstrating a very low-key way of retrieving any LVAR value. This can be done by making use of a Client Data Area that is shared among the WASM and external SimConnect client. I have a sample code that functions as a minimum working sample hosted here: msfs-wasm-lvar-access/custom_events at main · markrielaart/msfs-wasm-lvar-access · GitHub.

Note that contrarily to my previous attempts, I refrained from using the #0x11000-#0x1FFFF event ID range and instead followed the suggestion to use masked (?) events. That should provide more compatibility towards the work of other developers.

I am well aware of the developments discussed a closely related topic: Communication between WASM plugin and external application. That topic contains a number of references to other projects that basically deal with the same issue: reading and writing to LVARs and triggering flight deck buttons through a standalone SimConnect client. The reason I share my own low key code is that it should enable anybody to grasp the bare minimum of getting this to work, independent of other third-party software or interfaces.

Hope it may serve you well as a stepping stone in your own project.

Best,
Maurice

8 Likes

Thanks, I downloaded your Github repository for this project !

Glad it helps, please do note that the release zip folder at Github does not reflect the changes published in the linked folder yet. You have to compile it for yourself at this moment.

can this be called from C# app?

Hi @ZenMusic2, I am not sure if I follow. The idea of the published code is to get anybody started on a WASM+SimConnect project that may help them to interface custom electronics/parts. I imagine that if you know C# syntax it is not to hard to rewrite the shared code into that language? The purpose of the code that I shared above is to provide the required steps to get the WASM and SimConnect client work together and with the MSFS data. Hope that help? I’ll look into it later to share a more detailed readme for the code.

Best,
Maurice

Hi all,

thanks so much for the great work in this forum and especially to VFRMau aka Maurice for the superb examples.

I’m working on my own little home cockpit elements. I try do really “do it myself”. Means: I create the physical elements and the software for the arduinos and the interface to the sim.

For simvariables everything works. Now I need to access LVars and Hvars. I compiled the examples and tried to follow along. Yersterday I managed to code the client side in C# and have it up an running. So I think I understood the code.

Today I tried to exend the example to more then one variable. I understood, that the example uses the event to tell the server wich LVar it should communicate. I would like to go another way and define a dataset of a number of Lvars that is communicated every second.

Now I am confused about how to handle multiple variables at once. The documentation points out: “One client area can be referenced by any number of client data definitions.”

Does anybody know how that is done?

Or should I create a ClientDataArea for each Lvar? (Documentation: “There is no maximum number of client data areas, but the total must not exceed 1Mbyte.”)

I would appreciate to hear from you!

Best,
Stefan

Hi there,

Ha! I got it. It was simpler than I thought:

I just defined “data” as an array;
Original: double data = 1.;
New: double data[2];

Then I filled the elements of the array with values. And that is all!!!

On the client side I get the information in a struct of doubles.

So simple!

Again: MANY THANKS FOR THE FABULOUS EXAMPLE

Cheers.
Stefan

Would you share the C# version? That’s my next project.

I will.

Today I started to clean the code and comment everyting. Then I received the current Update for the sim. Now the WASM module causes a crash to desktop when the sim starts.

I updated the SDK and tried an empty WASM. That also causes the sim to crash.

So right now, I’m out of order. I have no clue how to make my modul run again.

No way to test and experiment.

The discussion in this forum gets hot. It seems that I’m not the only one…

As sonn as I’m back up, I will finish the work on the code and share it.

SDK 12 ? others have reported crashes and needed to move project to community folder

SDK 0.12.0.0 & update 1.15.7.0 | FSDeveloper

Yes. This is the only way I can get back to work. Try it yourself.
Thank you, yes it does seem to work OMG

I just put the entire project directory in community folder.

Like Reply

Report

Hi,

here comes a C# example for a client.

  • Create an empty C# application for FS as described in the SDK documentation.
  • I always need to set the properties of the project to Build X64 in order to compile correctly
  • Use Winforms and create a textbox named Statustext. Set the textbox to multiline.

I basically used the server by VFRMau for this example. I did some minor changes to the wasm Server Module:

  • I renamed the client data area from “EFIS_CDA” to “ClientDataArea_1”
  • I added a second value to the client data area:
// Definition of the client data area format. 
// double data = 1.;
double data[2];

FLOAT64 lvarValue;
lvarValue = get_named_variable_value(lvarID);
data[0] = lvarValue;
data[1] = 99.999;

Here comes the C# Code.

Generally speaking it’s almost identical with the c++ implementation.

BUT there is one magic trick which is often overseen:

We need to declare a struct for the data, that we want to receive and we MUST register this struct via RegisterStruct(…);

If this step is missing, we will never get data that make any sense.



        // This struct will receive the data that is received from the sim 
        // It's necessary to provide the struct. If this step is skipped, the
        // returned vaules won't make sense.
        // The struct can also be used to name the informations. 
        // IHMO it's always double. At least when using c# (?? Not sure ??)
        struct ClientDataStruct
        {
            public double HEADING;
            public double Dummyval;            
        };

(...)

          MySimconnectObject.RegisterStruct(CLIENT_DATA_DEFINITION_IDs.CLIENT_DATA_1);

This is the complete code of my simple example:

  
 using System;
 using System.Windows.Forms;

// For FS
using Microsoft.FlightSimulator.SimConnect;
using System.Runtime.InteropServices;


namespace WASMClientCsharp
{
    public partial class Form1 : Form
    {

        SimConnect MySimconnectObject = null;

        const int WM_USER_SIMCONNECT = 0x0402;

        // ID of the client Data definition
        enum CLIENT_DATA_DEFINITION_IDs
        {
            CLIENT_DATA_1
        }

        // REQUEST_IDs
        // This ID will be returned by the sim in the event.       
        enum REQUEST_IDs
        {
            REQUEST_1
        }

        // EVENT_IDs
        // Every event needs it's own ID
        // To fire an event, the ID is used.
        enum EVENT_IDs
        {
            WASM_EVENT_1
        }

        // Unique ID for the client data area
        // Remark: A client data area can contain an array of data. 
        //         We can pack multiple vaules into the data array
        //         and communicate those vaules together. So this
        //         ID doesn't represent an single variable!
        enum CLIENT_DATA_AREA_IDs
        {
            CLIENT_DATA_AREA_1
        }

        // EVENTs can be assigned to a group. That is a convenient way to 
        // set the priority for the events. 
        enum EVENT_NOTIFICATION_GROUP_IDs
        {
            EVENT_NOTIFICATION_GROUP_1
        }

        // This struct will receive the data that is received from the sim 
        // It's necessary to provide the struct. If this step is skipped, the
        // returned vaules won't make sense.
        // The struct can also be used to name the informations. 
        // IHMO it's always double. At least when using c# (?? Not sure ??)
        struct ClientDataStruct
        {
            public double HEADING;
            public double Dummyval;            
        };


        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {                      
            try
            {
                WriteStatus("Connecting to sim ...");
            
                // Create a SimConnect Object and connect to the sim
                MySimconnectObject = new SimConnect("My_ConnectionName", this.Handle, WM_USER_SIMCONNECT, null, 0);

                WriteStatus("Object successfully created.");

                // The Simconnect oject offers a number of events. The most important is probably "OnRecvOpen". We
                // get the event, when the connection to the sim is successfully established.
                MySimconnectObject.OnRecvOpen += MeinSimconnectObject_OnRecvOpen;
            }
            catch
            {
                WriteStatus("Object creation failed.");
            }
          
        }

        // The following methode is needed in order to catch the events from the Windows OS
        // If this method is missing, we do not get any event from the sim
        // Remark: Works only for forms applications. Not for WPF. WPF need a slightly different solution
        protected override void DefWndProc(ref Message m)
        {
            if (m.Msg == WM_USER_SIMCONNECT)
            {
                if (MySimconnectObject != null)
                {
                    MySimconnectObject.ReceiveMessage();
                }
            }
            else
            {
                base.DefWndProc(ref m);
            }
        }

        // Just a little helper, to write the status text into a textbox. 
        private void WriteStatus(string message)
        {
            Statustext.Text += message + "\r\n";
        }

        // This event will be called, when the Simconnect Object has established a connection to the sim. 

        private void MeinSimconnectObject_OnRecvOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data)
        {            
            // The sim sends some data in the data object. For example the version of the Sim.
            WriteStatus("OnRecvOpen ist erreicht. Version: V" + data.dwApplicationVersionMajor +"."+ data.dwApplicationVersionMinor);
           
            // Map a clientdata name to a client data area ID. 
            // The name MUST be the same as it's used in the WASM Module. 
            MySimconnectObject.MapClientDataNameToID("ClientDataArea_1", CLIENT_DATA_AREA_IDs.CLIENT_DATA_AREA_1);

            // Then we need to add some memory space for the client data area. 
            // Note: Convert.ToUInt32(Marshal.SizeOf(typeof(ClientDataStruct))) determines the size of the struct. 
            //       In this example with two double values the size will be 16 (bytes);
            MySimconnectObject.AddToClientDataDefinition(CLIENT_DATA_DEFINITION_IDs.CLIENT_DATA_1, SimConnect.SIMCONNECT_CLIENTDATAOFFSET_AUTO, Convert.ToUInt32(Marshal.SizeOf(typeof(ClientDataStruct))), 0.0f, SimConnect.SIMCONNECT_UNUSED);
            
            // THIS IS THE SECRET SAUCE!!!
            // The following step is mandatory for C# implementations. We need to register the struct in order to get data that make sense. 
            // If this step is missing. There will be events with data. But none of them will be equal to the data that have been sent. 
            MySimconnectObject.RegisterStruct(CLIENT_DATA_DEFINITION_IDs.CLIENT_DATA_1);

            // Then we create the client data area
            MySimconnectObject.CreateClientData(CLIENT_DATA_AREA_IDs.CLIENT_DATA_AREA_1, Convert.ToUInt32(Marshal.SizeOf(typeof(ClientDataStruct))), SIMCONNECT_CREATE_CLIENT_DATA_FLAG.DEFAULT);
            
            // Then we need an event, that will be called by the sim with the data. 
            MySimconnectObject.OnRecvClientData += MySimconnectObject_OnRecvClientData;
            

            // Then we initiate the Request of the client Data area.           
            MySimconnectObject.RequestClientData(CLIENT_DATA_AREA_IDs.CLIENT_DATA_AREA_1
                , REQUEST_IDs.REQUEST_1
                , CLIENT_DATA_DEFINITION_IDs.CLIENT_DATA_1
                , SIMCONNECT_CLIENT_DATA_PERIOD.SECOND          // In this case: We want to receive the data every second. 
                , SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.DEFAULT   // No matter if the data has changed. 
                , 0
                , 0
                , 0);

            // In this example, we need to send an event to the wasm module. 
            // This event is used to start the transfer of the LVAR values to the client data area .
            // *** I'm not happy with that solution. I will change that: The WASM module should read the LVARs periodically and write into the 
            //     Client Data area. But for now, I leave it as it is. 
            MySimconnectObject.MapClientEventToSimEvent(EVENT_IDs.WASM_EVENT_1, "LVAR_ACCESS.EFIS");
            MySimconnectObject.AddClientEventToNotificationGroup(EVENT_NOTIFICATION_GROUP_IDs.EVENT_NOTIFICATION_GROUP_1, EVENT_IDs.WASM_EVENT_1, true);
            MySimconnectObject.SetNotificationGroupPriority(EVENT_NOTIFICATION_GROUP_IDs.EVENT_NOTIFICATION_GROUP_1, SimConnect.SIMCONNECT_GROUP_PRIORITY_HIGHEST);
        }


        // This is the event, that will occur every second and provide the data. 
        private void MySimconnectObject_OnRecvClientData(SimConnect sender, SIMCONNECT_RECV_CLIENT_DATA data)
        {
            // Note: data provides more information: data.requestID for example provides the Request ID of the RequestClientData call. 
            
            // Now we create a data struct in order to read the data
            ClientDataStruct ReadClientDataStruct = (ClientDataStruct)data.dwData[0];
                    
            // From this point on, we can use the names that we declared in the struct in order to access the values
            // Note that there can be multiple values. If I understand the documentation correctly, we can use 512 double 
            // values in one client data area. If we need more data we need to create multiple client data areas. 
            WriteStatus("Data received: Heading: " + ReadClientDataStruct.HEADING);
            WriteStatus("Data received: â– â– â– â– â– : " + ReadClientDataStruct.Dummyval);

            // For this example we need to fire an event to the wasm module to initiate the transfer of the LVARS to the
            // client data area. 
            MySimconnectObject.TransmitClientEvent(SimConnect.SIMCONNECT_OBJECT_ID_USER, EVENT_IDs.WASM_EVENT_1, 2, EVENT_NOTIFICATION_GROUP_IDs.EVENT_NOTIFICATION_GROUP_1, SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY);
        }

    }
}

 

I hope that somebody can use this information.

1 Like

last update broke this, did the hotfix solve it?

Hey,

I’m a C# developer but seem to be having issues getting this working. I have almost no c++ experience and am a little confused. I compiled the WASM module and see that it loads in the MSFS console but am unable to communicate with it. Lets say I wanted to get something simple like “Air Speed” How would I go about doing that? I have done this with just SimConnect but not so mush with the WASM module. Any help would be appreciated!

2 Likes

This is not working after the last updates

Hello @GrolaG,

This post is already a few months old, but you seem the only on the web that has some knowledge about RegisterStruct.

There seem to be 2 signatures in the SimConnect meta data:

public void RegisterDataDefineStruct<T>(Enum dwID);
public void RegisterStruct<RECV, T>(Enum dwID) where RECV : SIMCONNECT_RECV;

But your code seems not to use any of them - I’m missing the “<…>”?

MySimconnectObject.RegisterStruct(CLIENT_DATA_DEFINITION_IDs.CLIENT_DATA_1);

And even in case you only provide the ID, shouldn’t you be using the “RegisterDataDefineStruct”.

Is there anybody out there that has some more information on these 2 functions? The SDK Documentation is really limited. And google is of no help here. Ok, I might not need to know the internals, but what is the difference between both functions?

1 Like