SimConnect + WASM combined project using VS2019

Addendum 1: Some more on Events

I’m planning to add some addendum’s in this post, if I discover some new things, or have some improvements or major changes. You can always find the latest files on HansBilliet/SimConnectWasmHUB: SimConnect and WASM tryout (github.com).

1. Improved Event implementation

I did a few more investigations on Events. I now realize that my implementation was not wrong (it works), but not really good practice.

Triggering an Event is done in the switch (vInList.cType) / Case 'K' in SimConnectHUB.SetVariable with the below code:

case 'K':
    if (v.sName.IndexOf('.') == -1)
        try
        {
            _oSimConnect.TransmitClientEvent(
                0, 
                (EVENT_ID)vInList.uDefineID, 
                (uint)dValue, 
                (EVENT_ID)SimConnect.SIMCONNECT_GROUP_PRIORITY_HIGHEST, 
                SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY);
            LogResult?.Invoke(this, $"{vInList} SimConnect Event triggered with {(sValue == "" ? "no" : "")} value {sValue}");
        }
        catch (Exception ex)
        {
            LogResult?.Invoke(this, $"SetValue SimConnect Event Error: {ex.Message}");
        }
    else
    {
        if (sValue == "")
            SendWASMCmd($"HW.Set.(>K:{v.sName})");
        else
            SendWASMCmd($"HW.Set.{sValue} (>K:{v.sName})");
        LogResult?.Invoke(this, $"{vInList} Custom Event triggered with {(sValue == "" ? "no" : "")} value {sValue}");
    }
    break;

My implementation is kind of making a detour to the WASM Module for Custom Events (sName contains a “.”). But that is definitely not needed, and even a dumb thing to do! Honestly, I did that because I misinterpreted some answer I got on the forum.

I can simplify the code as below:

case 'K':
    try
    {
        _oSimConnect.TransmitClientEvent(
            0, 
            (EVENT_ID)vInList.uDefineID, 
            (uint)dValue, 
            (EVENT_ID)SimConnect.SIMCONNECT_GROUP_PRIORITY_HIGHEST, 
            SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY);
        LogResult?.Invoke(this, $"{vInList} SimConnect Event triggered with {(sValue == "" ? "no" : "")} value {sValue}");
    }
    catch (Exception ex)
    {
        LogResult?.Invoke(this, $"SetValue SimConnect Event Error: {ex.Message}");
    }
    break;

But if we also want to use TransmitClientEvent with our Custom Events, then we will need to give them an ID as well using MapClientEventToSimEvent. That was not needed if we sent them to our WASM Module, where the Event was triggered using the versatile execute_calculator_code.

That means that we will also register our Custom Events (before we didn’t do that).

case 'K':
    // Register a K-event
    try
    {
        v.SetID(VarData.AUTO_ID, VarData.NOTUSED_ID);
        // Registration of a SimConnect Event
        _oSimConnect.MapClientEventToSimEvent((EVENT_ID)v.uDefineID, v.sName);
        //_oSimConnect.TransmitClientEvent(0, EVENT_ID.MY_EVENT, 4, (EVENT_ID)SimConnect.SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY);
        LogResult?.Invoke(this, $"{v} SimConnect Event added and registered");
    }
    catch (Exception ex)
    {
        LogResult?.Invoke(this, $"Register K-event Error: {ex.Message}");
    }
    break;

The above changes result in simpler code. All Events are handled the same way. SimConnect will take care of the dispatching between SimEvents or Custom Events.

2. My own Custom Event

In my opinion, Events are very handy to reduce communication. Reason is that you call TransmitClientEvent using a simple ID, but this could trigger several commands. To demonstrate this, I added a Custom Event in my WASM Module.

Below addition in the module_init will add the Custom Event “HABI_WASM.EVENT_TEST”, adds it in the group HABI_WASM_GROUP::GROUP, and finally sets the highest priority on this group.

// file WASM_HABI.cpp

enum eEvents
{
	EVENT_FLIGHT_LOADED,
	EVENT_FRAME,
	EVENT_TEST,   // <-- this has been added


enum HABI_WASM_GROUP
{
	GROUP
};

...

extern "C" MSFS_CALLBACK void module_init(void)
{
        ...

	// Add EVENT_TEST and let it do something
	hr = SimConnect_MapClientEventToSimEvent(g_hSimConnect, EVENT_TEST, "HABI_WASM.EVENT_TEST");
	if (hr != S_OK)
	{
		fprintf(stderr, "%s: SimConnect_MapClientEventToSimEvent failed.\n", WASM_Name);
		return;
	}
	hr = SimConnect_AddClientEventToNotificationGroup(g_hSimConnect, HABI_WASM_GROUP::GROUP, EVENT_TEST, false);
	if (hr != S_OK)
	{
		fprintf(stderr, "%s: SimConnect_AddClientEventToNotificationGroup failed.\n", WASM_Name);
		return;
	}
	hr = SimConnect_SetNotificationGroupPriority(g_hSimConnect, HABI_WASM_GROUP::GROUP, SIMCONNECT_GROUP_PRIORITY_HIGHEST);
	if (hr != S_OK)
	{
		fprintf(stderr, "%s: SimConnect_SetNotificationGroupPriority failed.\n", WASM_Name);
		return;
	}

        ...
}

In a SimConnect Client, we can now use MapClientEventToSimEvents to map the Event “HABI_WASM.EVENT_TEST” to an EventID, and then trigger it using TransmitClientEvent with this EventID. Next to the eventID, TransmitClientEvent has also a parameter dwData. We can use this as extra information to send with the Event (in my implementation, this is entered in the Value field).

In the WASM Module, Events are captured in the MyDispatchProc with SIMCONNECT_RECV_ID_EVENT.

case SIMCONNECT_RECV_ID_EVENT:
{
	SIMCONNECT_RECV_EVENT* evt = (SIMCONNECT_RECV_EVENT*)pData;

	if (evt->uEventID == EVENT_TEST)
	{
		DWORD d = evt->dwData;
		char sCmd[1024];

		sprintf(sCmd, "%u (>L:A32NX_EFIS_L_OPTION, enum) %u (>L:A32NX_EFIS_R_OPTION, enum)", d, d);
		execute_calculator_code(sCmd, nullptr, nullptr, nullptr);
		fprintf(stderr, "%s: execute_calculator_code(\"%s\"))\n", WASM_Name, sCmd);
	}
	break; // end case SIMCONNECT_RECV_ID_EVENT
}

Here you see the power of Events. In my WASM Module, it executes 2 commands using dwData as a parameter.

In the MobiFlight WASM Module that you can find here, Events are loaded during intialization of the WASM Module. They are defined in a file “modules/events.txt” and the user can add its own events in the file “modules/events.user.txt”.

I’m thinking of using a decentralized way of working (not carved in stone yet). The principle would be that my individual cockpit hardware modules will know the commands it needs. When connecting, each hardware module will register this via the SimConnect Client, which will add the Event Name and the Event Command dynamically in the WASM Module using some registration command (we will need some addition in the protocol for that).

Bottom line is that in both cases, once the Event ID’s are defined, they can be used as a much more efficient way then sending the complete commands using “HW.Set.”.

Goto Addendum 2: Real “execute_calculator_code” tool