Update: Sending Events and other changes

I have been pretty busy these past days, although admittedly not all on CsSimConnect. That said, there are a number of major changes and additions:

  • Solution structure-wise, I bit the bullet and split off the “InterOp” DLL project. This project kept giving me headaches due to the simulator dependencies, which don’t exist in all the C# projects. There is now a “CsSimConnectInterOp.sln” solution file with a single project. (with the same name) I’ll clean up the original project so we’re back to two configurations: “Debug” and “Release”. The InterOp solution has the full six, two for P3Dv4, P3Dv5, and MSFS 2020 each. The configuration names are re-arranged so their names start with “Debug” or “Release”, because that apparently is something the tools check for.
  • I started a “CsSimConnectUIComponents” project to collect useful UI components and view-models, so eventually only that one and any Windows applications will contain WPF dependencies.
  • Similarly, a new library “SimScanner” will contain everything that has to do with interacting with the installed simulators. If it works as intended, this project will get all remaining Windows dependencies. All other projects should be .Net Core compliant, and as such should be able to run on macOS or Linux. Jim Rees’s (aka Dragonlaird) MSFS SDK would then become relevant to base a new InterOp implementation on. The SimScanner library contains a first implementation of an add-on scanner, and should allow you to e.g. build a list of all installed scenery. A Bgl sub-namespace will get the code to read BGL files so you could do things like building lists of airports or parkings. Another thing I’d like to add here is a scanner for aircraft models.
  • Two small testers: BglReader will open a Bgl file and attempt to read it. It currently is able to read airport ICAO codes. ListAddOns will build a complete list of all addons installed in Prepar3D. From here we can collect all installed Scenery or Aircraft.
  • A working UI app is “AutoPilotController”, which allows you to set some basic auto-pilot things while seeing a list of all aircraft flying. This is targeted at group flights, but was my testcase for sending events.

ClientEvents and Sending them

In SimConnect clients, all kinds of data is managed using local Ids, and events are no different. No matter if you’re trying to subscribe to important Simulator events or events related to simulated objects, you first have to introduce a “Client Event Id”. This Id is a number you have to provide yourself, and then bind to something Simulator-related. For example, if you want to toggle the “Autopilot Master” switch, you have to send an event. Actually, this is a major inconsistency in the way you communicate with the simulator, because to find out what the current state of that switch is, you’ll have to request it as data and the definition below will do just that.

[DataDefinition("AUTOPILOT MASTER", Units = "Bool", Type = DataType.Int32)]
public bool AutoPilotMaster { get; set; }

Ok, it just so happens that this particular item is settable, but for example “approach mode active” isn’t. What you are supposed to do is send an event, which is meant to correspond with a button push or keypress, where buttons can be interpreted as either controller buttons and graphical buttons in the simulator. And that last bit can indeed be taken literally, because there are events for each and every button in 2D or Virtual Cockpit, and the value sent along can stand for a particular kind of click on the mouse, or even the rotating of the mouse-wheel. As a consequence, you can use events to simulate someone turning a knob in the virtual cockpit, or use the same event id for directly setting a value on the associated instrument.

The EventManager in CsSimConnect is now finally getting some heat, because you can use it to create ClientEvent objects.

private static readonly ConcurrentDictionary<string, ClientEvent> clientEvents = new();

public static ClientEvent GetEvent(string eventName)
{
    return clientEvents.GetOrAdd(eventName, name => new(Instance, name));
}

This method will give you a ClientEvent for an event identified by its name. When you attempt to use it, it will generate a new id and map the name to that id, unless this was already done of course. So far the manager has a default group, and any event not explicitly placed in another group will join this one. You then have two ways to actually send the event:

public void Send(uint objectId = 0, uint data = 0, Action<SimConnectException> onError = null)
{
    mgr.SendEvent(this, objectId, data, onError);
}

public void SendSigned(uint objectId = 0, int data = 0, Action<SimConnectException> onError = null)
{
    mgr.SendEventSigned(this, objectId, data, onError);
}

The simple reason for this is that the actual parameter must be an unsigned integer (it is a DWORD in fact) even though there are events that can take negative numbers. (Vertical Speed anyone?) The onError callback is used for errors returned from the up-to three calls to SimConnect (MapClientEventToSimEvent, AddClientEventToNotificationGroup, and TransmitClientEvent) as well as any exception being sent back related to those same three calls. Still to do is how to do a cleanup, because we can only get a call back from the Simulator if anything fails. If all goes as requested, we’ll never hear from it. Probably do something with a timeout, although that is ugly.

Subscribing to events was pretty easy, because we already had the simulator events, so streaming client events was pretty straightforward.

Next up: Stream Cleanup and maybe a BeanManager

Streams still have some cleanup to do, especially concerning the IEnumerable and IEnumerator implementations. Also, I’ll revisit System.Reactive, because I may have learned enough about Reactive Programming in C# that I can finally decide if it will fit. It would allow me to junk a lot of home-grown code that they have probably done better already, but I need to see what that does to the API.

A thing I’ve started missing is a good Bean-Manager, comparable to what Java does with CDI and Spring. I’d need to find a good set of application lifecycle events to hook in to, but it would help a lot if a method annotated with e.g. “[SimEventHandler]” or “[SimEventHandler(SystemEvent.ObjectAdded)]” would automatically get subscribed. A consequence could be that this only works if we’re in control of making that object, which is where the “BeanManager” bit comes in. Scanning all assemblies to find appropriate “[Autowired]” spots isn’t the problem, being involved in any “new()” happening at runtime is more what I’m worrying about. And yes, I know the .Net ecosystem has something for this, but it only works in the context of ASP.Net, or requires a non-Core Microsoft namespace.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s