Demo: LVAR write access for any aircraft control

I see, this approach didn’t even cross my mind. Definitely seems like the way to go - we will keep this in mind!

Umberto: I like the idea with the dynamic registration. i just haven’t had the time yet to try it. The current approach for the MobiFlight module is to get people started and continuously improve. Contributions are welcome. With the latest version i changed everything to dynamic code execution which also allows for writing to LVars.

Ideally the config files on the WASM Module will be substituted through a approach like your describing.

On the client side - where you’d like to manage the assignments - a dynamic method would also be great to present the available events for a specific aircraft in a UI and let the user chose.

The question however that really bugs me: Why do we have to do all these workarounds and heavy lifting in the first place? Why can Asobo not put somebody on this topic and come up with a developer friendly way via the API.

Please let’s join forces and help make it work and improve in the meantime.

3 Likes

BTW, the flat file approach right now actually has the advantage that you can put all config statically in there which allows other tools like even FSUIPC and StreamDeck to interact with MSFS2020. For these tools a dynamic-only way to register events would not work because the clients don’t allow for such.

I wonder if we could have a hybrid approach then - have a flat file that a user can configure somehow (so it works with these tools), in addition to dynamic registration for everything else. Would love to see some of the code for the dynamic registration.

Cool if you have the dynamic part figured out, please share it with us.

2 Likes

I keep asking for that since the beginning of Alpha, but Asobo seems to give no attention to small freeware devs :cry:. I don’t know if FlyByWire team or such larger team would be able to get any traction on this.

1 Like

I’ve made good progress on my WASM module + client API. For lvars, I am scanning to find what is available for the loaded aircraft, and making them available to the client. For hvars, I haven’t found a way to do this programmatically yet (any ideas?), so i am using aircraft-specific files (on substring match) to load the available hvars, and these are also passed back to the client. So the client needs to know nothing about lvars/hvars, these are all passed via client data areas.
I have a separate CDA for the lvar values. This is updated on regular intervals either from the WASM (on second, frame or visual frame) or from the client on request (specifiable in Hz). All lvar values are passed, and so the client holds an up-to-date lvar/value list, so lvar reads are client side-only.
Lvar writes either go via events (for shor or unsigned short values) or via a specific client data area for writing 64bit values.
I also have an additional client data area for executing calculator code passed as a string.
I am adding the final touches at the moment and am planning on a beta release of the WASM module + client-side API + test client sometime next week.

The current issues I am having are:

  • when to start scanning for lvars: Currently I’m waiting for for an un-pause event that follows a SimConnect open after a flight loaded event. I still get more lvars if I re-scan once the flight is loaded.
    I also get lvars registered for a previous aircraft when changing aircraft.
    What is the correct event (or sequence of events) to determine when an aircraft is fully loaded and the lvars are ready to be scanned?
  • no way to determine hvars programmatically. I am using aircraft-specific files at the moment.
    I have read that they can be found using the developer mode, although I haven’t tried this yet. If anyone knows a way to determine the available hvars programmatically, please let me know. Alternatively, if you know of available hvars for any aircraft, please let me know and I will start building the aircraft specific files to distribute with the WASM.

The API exposed to the client will have the following public methods:

> class WASMIF
> {
> 	public:
> 		static class WASMIF* GetInstance(HWND hWnd, int startEventNo = EVENT_START_NO, void (*loggerFunction)(const char* logString) = nullptr);
> 		static class WASMIF* GetInstance(HWND hWnd, void (*loggerFunction)(const char* logString));
> 		void start();
> 		void end();
> 		void createAircraftLvarFile();
> 		void reloadLvars();
> 		void setLvarUpdateFrequency(int freq);
> 		int getLvarUpdateFrequency();
> 		void setLogLevel(LogLevel logLevel);
> 		double getLvar(int lvarID);
> 		double getLvar(const char * lvarName);
> 		void setLvar(unsigned short id, double value);
> 		void setLvar(unsigned short id, short value);
> 		void setLvar(unsigned short id, unsigned short value);
> 		void setHvar(int id);
> 		void listLvars(); // Just print to log for now
> 		void listHvars(); // Just print to log for now
> 		void getLvarList(unordered_map<int, string >& returnMap);
> 		void getHvarList(unordered_map<int, string >& returnMap);
> 		void executeCalclatorCode(const char *code);

John

P.S. I should also say that you can still use the FSUIPC WASM with yout own client API, but to do so you need to include and program your client with the WASM.h:

#pragma once
/*
 * Main WASM Interface. This is the file that is shared
 * between the WASM module and the Client.
 */
#define MAX_VAR_NAME_SIZE		56 // Max size of a CDA is 8k. So Max no lvars per CDK is
								   //   8192/(this valuw) = 146
#define MAX_CDA_NAME_SIZE		64 
#define MAX_NO_LVAR_CDAS		4 // Allows for 585 lvars (4*(8k/56)): 8 is max and allows for 1024 lvars, the max allowd for the value area (8k/8)
#define MAX_NO_HVAR_CDAS		4 // We can have more of these if needed
#define CONFIG_CDA_NAME			"FSUIPC_config"
#define LVARVALUE_CDA_NAME		"FSUIPC_SetLvar"
#define CCODE_CDA_NAME			"FSUIPC_CalcCode"
#define MAX_CALC_CODE_SIZE		256 // Up to 8k

// Define the default value where our events start. From this:
//    0 = Get Config Data (provided but shouldn't be needed)
//   +1 = Set LVAR (short values): parameter contains LVAR ID in low word and encoded value in hi word
//   +2 = Set HVAR: parameter contains ID of CDA that holds the lvar in the low word, and the hvar index number in the hi word
//   +3 = Request to Update LVARS: ignored if WASM is updating internally.
//   +4 = Request to List LVARS (generates aircraft LVAR file)
//   +5 = Reload Control: scans for lvars and re-reads hvar files and drops and re-creates all CDAs accordingly
// Note that it should be possible to change this value (via an ini parameter)
// in both the WASM module and any clients. They must, of course, match.
#define EVENT_START_NO			0x1FFF0

#pragma pack(push, 1)
typedef struct _CDASETLVAR
{
	int id;
	double lvarValue;
} CDASETLVAR;

typedef struct _CDACALCCODE
{
	char calcCode[MAX_CALC_CODE_SIZE];
} CDACALCCODE;

typedef struct _CDAName
{
	char name[MAX_VAR_NAME_SIZE];
} CDAName;

typedef struct _CDAValue
{
	double value;
} CDAValue;

typedef enum {
	LVAR, HVAR, VALUE
} CDAType;

typedef struct _CONFIG_CDA
{
	char CDA_Names[MAX_NO_LVAR_CDAS + MAX_NO_HVAR_CDAS + 1][MAX_CDA_NAME_SIZE];
	int CDA_Size[MAX_NO_LVAR_CDAS + MAX_NO_HVAR_CDAS + 1];
	CDAType CDA_Type[MAX_NO_LVAR_CDAS + MAX_NO_HVAR_CDAS + 1];
} CONFIG_CDA;

#pragma pack(pop)

The A320 has the LVARs
A320_Neo_FCU_state and A320_NEO_FCU_STATE which create my problem. To me, this seems like a bug.

Logging the values for these LVARs ( in the ModelBehaviorsDebug window) shows me that the A320_Neo_FCU_State has a fixed value of 2 and that A320_NEO_FCU_STATE gives the correct state value depending on the ALT and VS states(Selected or Managed modes).

My problem is that when searching for the LVAR id they both come up with the same id and the id seems to be the id for the A320_Neo_FCU_State.

This will not let me use the A320_NEO_FCU_STATE value in the Client, as I can’t find the correct id for it.

How can this be solved?

I get them with different IDs:

Sun Feb 28 2021 15:37:20.990 [INFO]: ID=104 A320_Neo_FCU_State = 2.000000
Sun Feb 28 2021 15:37:20.990 [INFO]: ID=105 A320_NE0_FCU_STATE = 0.000000

?

Oh, now I see that you name the

A320_NE0_FCU_STATE

with a 0(digit zero), not an O(letter O) in NE0 name

That’s likely because registering an LVar is not immediate, especially if the airplane tried to register many at the same time, they’ll be queued and you can’t be sure the queue will be emptied before the flight loaded event completed.

And, nothing forces the airplane to register all its variables at start anyway, it can be done at any time.

The only reliable way to do this, is if there was a standard notification when a variable is registered and unregistered, but since there isn’t one, I don’t think you have many choices other then rescanning everything again, which of course is quite innefficent.

It seems we are back to square one: I tried to warn about the risk of everybody doing the same thing, and reinventing the wheel with its own version of the “WASM variables bridge” for external Simconnect applications, to prevent a future in which a typical user would have dozen of identicaly modules doing exactly the same thing, and collectively clobbering the Simconnect data pipes for everybody, but this seems to have falled on deaf hears.

I’m afraid the only way out of this is lobbying Asobo in providing Simconnect methods accessible to both external and internal clients now.

That’s likely because the previous aircraft never bothered to call the unregistrer variable functions so yes, it’s fairly difficult to know for sure which variables are still in use by the current airplane.

For developers, its the interface / API provided by the WASM bridge that is the relevant part. I have no problems switching to some sort of “standard” WASM module IF the interface supported the functionality that I require. As of now, I have not seen such an interface.

Even if that ever happens, I think it will be so far away that 3rd-party WASM modules are going to be needed for quite a while.

Ok, thats interesting. Mainly I am interested in capturing all the initial variables. I think I can get most if I issue a re-scan a couple of seconds after the initial list is received, but I’ll test further.

I allow for re-scanning on user/client request, which would allow for capture of lvars created on-the-fly. However, I’m not sure how useful this is, as I think using lvars that do not necessarily have the same lifetime as the aircraft would be difficult to handle…

That’s precisely my point, you just can’t do it reliably.

An airplane, or any module that has access to the register variable call, can decide to delay registration of variables at its leisure, nothing in the sim enforces this should be happening at any given time, and nothing compels to unregister a variable so, a scanning procedure would only give some idea of what is being registered at startup, it’s just a snapshot of the situation during that scan.

Also, a certain variable might be registered by a “3rd party”, that will just register it without using it, and something else might use it, without registering it.

The way I see it, the only 100% reliable way to do that, is with the help of the sim, like adding variable registration to the list of system Events a client can subscribe to, meaning you could just subscribe when a variable is being registered or unregistered but, of course, it wouldn’t make much sense without providing with features to access variables from any Simconnect client, not just WASM modules.

Hi,

In a different context, I also struggled to find a “universal” FS starting sequence. My C++ app manages a very simplified home cockpit, and my concern is to get the home cockpit and the sim consistent at startup; as an example, the “TBM 930” starts with the Pitot heat switch ON, whereas my home cockpit switch may be OFF.
I noticed that sending the Event “PITOT_HEAT_OFF” too early had no effect.
I also noticed that asking once for the value of the SimVar “TITLE” at some states could never be responded to, or even crash the sim.
So I traced the occurences of the System events that I considered as potentially pertinent: “AircraftLoaded”, “FlightLoaded”, “Pause”, “Paused”, “PositionChanged”, “Sim”, SimStart", “SimStop”, “Unpaused” and “View”.
My strategy is to detect the right sequence, then ask for “TITLE” (in case there are some aircraft specific things to do), then start my app loop and eventually send “PITOT_HEAT_OFF”.
Note that I suspect that as FS updates, there are some changes in the event sequence, therefore I am not sure that my current strategy is the simplest and that it will remain OK.

As for me, there are 2 cases.
The first case, you are probably not interested in, is during debugging, i.e. when my app starts when a flight is already running. In such a case, the sequence I use is “View”, then “Sim” with data == 1.

The second case, that must be close to yours, is when my app is loaded by FS, using the EXE.xml file. This is much more complicated, because you may either have “GENERAL/ACCESSIBILITY/SKIP PRE-FLIGHT CINEMATICS” ON or OFF, and, in case it’s OFF, you may or you may not be patient enough to let the flight start automatically (although, recently, I found that this was not important anymore).
Also, I found important to eliminate false starting sequences when I went back to “MAIN MENU” to start a new flight (this is when I could crash the sim).

With the FS version 1.13.17.0 (not sure, wasn’t it 1.13.16.0 the day before yesterday?), my sequence is:

  • “Sim” with data == 1
  • “PositionChanged”
  • “Unpaused”
  • “View”
    I cancel the sequence in progress on receipt of “SimStop”.
    When the sequence is complete, I ask once for “TITLE”, and on receipt, I start my app loop.

This is not mature, I even changed it this morning, before this post. Not that I’ll be away from the sim for a week, so no tests during that time.

Hope this helps. I am open to other ideas. And I am very interested in the LVAR matters, that I have not explored yet because my IDE is not MS Visual Studio, but Embarcadero’s C++ Builder.

I have now released an initial version of the FSUIPC WASM module + client API + test client. Please see WASM Module + Client API for MSFS / FSUIPC7 now available (for developers and advanced users only!) - Announcements - The simFlight Network Forums.

For comments/suggestions/discussion on this, please use the following:
FSUIPC WASM module + client-side API + lvar/hvar discussion topic - FSUIPC7 MSFS - The simFlight Network Forums

Still lacking documentation - I’ll at least add some READMEs to the github repos in the next few days.

John

1 Like

One of our developers at FBW has been working on a central module and specification to accomplish interfacing with LVar’s and H-events via SimConnect. The work-in-progress can be found here: GitHub - theomessin/jetbridge: 🔗 Microsoft Flight Simulator module to allow out-of-process SimConnect clients to access MSFS inner workings as if they were in-process.

When completed, we intend to offer this as a quick and simple download option via the FBW Installer, and it will work with any aircraft, and interface with external hardware/programs as long as they follow the specification. It only relays whatever information the client specifies, so it has a very minimal, if any, performance impact.

We hope this will become the standard for LVar and H event communication until Asobo implements native support, if at all.

5 Likes

Thanks for the great work VFRMau !

I tried that but I’m still facing an issue.

I have an external program that registers and transmits an 0x11000 event :

hr = SimConnect_MapClientEventToSimEvent(hSimConnect,EVENT_TOGGLE_BARO_UNITS,“0x11000”);
SimConnect_TransmitClientEvent(hSimConnect, SIMCONNECT_OBJECT_ID_USER, EVENT_TOGGLE_BARO_UNITS, 0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);

I see in debug that event in the debug console :

Unfortunatly, it doesn’t get into my switch case. I logged before the switch and get some output so I’m sure the eventHandler is registered :

static void FSAPI EventHandler(ID32 event, UINT32 evenData, PVOID userData) {
switch (event) {
case BARO_TOGGLE_UNITS:
fprintf(stderr, “In BARO_TOGGLE_UNITS event”);
ID baroSelector_ID = check_named_variable(“XMLVAR_Baro_Selector_HPA_1”);
FLOAT64 baroSelector_value = get_named_variable_value(baroSelector_ID);
fprintf(stderr, “In Baro selector value %f”, baroSelector_value);
set_named_variable_value(baroSelector_ID, !baroSelector_value);
}
}

register_key_event_handler((GAUGE_KEY_EVENT_HANDLER)EventHandler, NULL);

Any hint on that ? thanks :wink:

PS: Sorry the preformated text did not work

Hi,

Great to see so many follow-ups since the original post here, I’m glad it could help out so many of you apparently. It shows that the need for a uniform method is an actual issue, and I hope one way or another it will be getting there.

@Tzoreol, to answer your question specifically, have you double checked to include the (#)-sign in front of the hex-code, when calling the event in SimConnect_MapClientEventToSimEvent? That could be a typical overlooking that causes an event not to be triggered. I think I mentioned something along these lines earlier on in this topic as well.

Good luck!
Maurice

I did not and indeed it was missing the # !
Works like a charm now. Thank for your work and for taking the time the answer!

1 Like

Hello. I am not a programmer at all and therefore I do not understand all the nuances that are discussed in this thread very well. Therefore, I ask for help.
For example, there is Lvar B747_8_MFD_Range = X. (From zero to eleven). I understand that now there is no way to assign a joystick key to such a function using, for example, FSUIPC, but judging by the first post in this thread, you somehow do it. Compile something. May I ask for a simple example for a layman how to assign a key to any Lvar?
I apologize also for my english. All this will soon blow my poor head off.
UPD: I have already installed SDK, VS and compiled a .wasm file, but I still don’t understand how to call this function and assign it to a key.