X-Plane Plugins – Some Notes
I’ve been stumped for the last few weeks with getting my Raspberry Pi FMC to work with Zibo’s 737. I used the ExtPlane plugin to interface my code on the Pi with the simulator, and it works well for almost all FMCs except Zibo’s. The symptoms would be missing or truncated data updates, causing the screen on the Pi to get messed up.
Since I’m only working on the Pi end, it’s hard to tell whether the problem is with ExtPlane or Zibo. The AirFMC iPad app, which comes with its own plugin, is able to replicate the Zibo screen without any issues, so I thought I should try to write a plugin to access the Zibo datarefs and see what that looks like. If so, then I could probably extend that data to the network and have my Pi code get to the data.
So the first stop is to download the X-Plane SDK’s Hello World plugin. This comes in three flavours: an XCode one for Mac OSX, a Visual Studio 2010 version for windows, and a GCC Linux version. I grabbed the Linux version, as that’s what I run on my development PC (I also have an X-Plane install here for testing stuff like this). The Linux version is the easiest to build as you just have to run make (the SDK is embedded in the example). It compiled the example C program and linked it against the SDK, and built a shared library under Hello-World-SDK-3 called lin.xpl. I symlinked the Hello-World-SDK-3 folder into X-Plane’s Resources/plugins folder, and started up X-Plane, and got this:
Analyzing Hello-World-SDK-3.cpp
Let’s take a look at the Hello-World-SDK-3.cpp …
Each plugin is required to declared the following five C functions:
- int XPluginStart (char * outName, char * outSig, char * outDesc)
- This is called when the plugin is started. For a global plugin (one which runs regardless of what aircraft or scenery is being used, and is found in the Resources/plugins folder), this is usually called while X-Plane itself is starting up.
- The minimal the plugin must do is copy the plugin name, a unique plugin identifier, and plugin description into the char buffer pointers provider (up to 256 characters).
- The plugin can then do any other stuff it wants to do during startup. In the case of Hello-World-SDK-3, it uses the API’s from XPMLDisplay.h and XPMLGRaphics.h to create a window withing X-PLane.
- void XPluginStop (void)
- This is called when the plugin is stopped.
- In the Hello-World-SDK-3 example, the window created is destroyed.
- int XPluginEnable (void)
- This doesn’t do anything in Hello-World-SDK3.
- void XPluginDisable (void)
- This doesn’t do anything in Hello-World-SDK3. It just returns 1.
- void XPluginReceiveMessage (XPLMPluginID inFrom, int inMsg, void * inParam)
- This doesn’t do anything in Hello-World-SDK3.
On it’s own, other than declaring stuff, the plugin doesn’t get to do anything much, unless it registers callbacks to itself. It is in the callback that the plugin gets to “do its thing”. For example, in the case of Hello-World-SDK3, when the window is created, a list of callbacks are specified which trigger when the following happen:
- the window needs to be drawn
- the user clicks in the window
- the user right clicks in the widow
- a key is pressed in the window
- the cursor is moved in the wondow
Most of these are set to a dummy handler, except for the draw window function, in which Hello-World-SDK-3 writes “Hello World” within the window.
There are many other ways that a plugin can register a callback, depending on what is needed. A common callback used for plugins that need to do stuff regularly is to use the XPLMProcessing API, where callbacks can be tied into X-Plane’s flight loop.
Adding “GOT HERE”
The next thing i did was add #include <stdio.h> at the top of Hello-World-SDK3, and sprinked some printfs around the existing functions and callbacks. I then recompiled the plugin and launched X-Plane from a terminal. This gave me some confidence that I could trace the plugin and get output, and also some idea of when each callback is called.
Interestingly, the drawWindow function got called, even when the simulator was still at the main menu.
So what’s the difference between Start and Disable? Start is more for declaring resources your plugin provides, and enable is more for making use of resources that other plugins or the sim provides. This way, there is less chance of requiring a resource that hasn’t been declared yet.
I only got XPluginDisable() and XPluginStop() calls when X-Plane was shutdown. Incidentally, I saw two calls to XPluginReceiveMessage() … so this is probably something that should be looked at.
Adding a Processing Callback
Let’s modify Hello-World-SDK3 to have a Processing callback. First we need to include the appropriate headers with a
#include “XPLMProcessing.h”.
Then I added a function that will be called every 1 second:
PLUGIN_API float flight_loop_callback ( float inElapsedSinceLastCall, float inElapsedTimeSinceLastFlightLoop, int inCounter, void * inRefcon) { printf ("SHA: in flight_loop_callback\n"); return 1.0; };
This is then registered inside the XPluginEnable() function with:
// register a FlightLoopCallback XPLMRegisterFlightLoopCallback ( &flight_loop_callback, 1, NULL );
And deregistered inside XPLuginDisable() function with:
XPLMUnregisterFlightLoopCallback ( &flight_loop_callback, NULL );
Now when X-Plane is run I get this output every 1 second:
SHA: in flight_loop_callback SHA: in flight_loop_callback SHA: in flight_loop_callback
Note that this only starts when the sim is running proper i.e. it is not called when at the X-Plane menu.Â
Fetching a Dataref
Next, let’s see if we can fetch a dataref inside our flight_loop_callback.
It seems that you have to poll regularly to get the value of a dataref … there is no way to do a callback when a dataref changes value (Unless it is a “shared” dataref. I”m not sure if Zibo’s datarefs are shared or not, so lets assume it’s owned by the zibo plugin).
Also to access datarefs, you have to call XPLMFindDataRef with the dataref string. This then returns a XPLMDataRef, which can then be interrogated at any time to get the value of the dataref.
So coming back to our hacked-up Hello-World-SDK-3 code, we add in the header for data access:
#include "XPLMDataAccess.h"
We also declare a global to hold the dataref:
XPLMDataRef l4m;
In the enable method, I added some code to find a dataref (one I was struggling with on ExtPlane/Zibo):
// find a dataref l1m = XPLMFindDataRef ("laminar/B738/fmc1/Line01_M"); if (l1m != NULL) { // check its datatype printf ("Found laminar/B738/fmc1/Line01_M [type %d]", XPLMGetDataRefTypes (l1m)); } else { printf ("Couldn't find laminar/B738/fmc1/Line01_M"); }
Now in our flight_loop_callback, let’s get the value of this dataref:
if (l1m != NULL) { char buffer [40]; int c = XPLMGetDatab ( l1m, buffer, 0, 40 ); buffer[c] = ''; printf ("Got %d [%s]", c, buffer); }
When I first ran this … it couldn’t find the dataref. The reason for this that zibo’s FMC is an aircraft plugin, which means it is initialized when the aircraft is first loaded. However, Hello-World-SDK3 is a global plugin, and gets loaded way before that. Therefore, Zibo’s plugins aren’t available during the time the Hello-World-SDK3’s XPluginEnable method is called.
I modified it by moving the above code into flight_loop_callback():
PLUGIN_API float flight_loop_callback ( float inElapsedSinceLastCall, float inElapsedTimeSinceLastFlightLoop, int inCounter, void * inRefcon) { printf ("SHA: in flight_loop_callbackn"); if (l4m == NULL) { // find a dataref l4m = XPLMFindDataRef ("laminar/B738/fmc1/Line01_M"); if (l4m != NULL) { // check its datatype printf ("Found laminar/B738/fmc1/Line01_M [%d]n", XPLMGetDataRefTypes (l4m)); } else { printf ("Couldn't find laminar/B738/fmc1/Line01_Mn"); } } if (l4m != NULL) { char buffer [40]; int c = XPLMGetDatab ( l4m, buffer, 0, 40 ); buffer[c] = ''; printf ("Got %d [%s]", c, buffer); } return 1.0; };
Now, when the aircraft loads and the sim proper starts, I get:
Got 1 [ ]SHA: in flight_loop_callback Got 1 [ ]SHA: in flight_loop_callback
and after fiddling with the FMC:
Got 24 [WMKC ]SHA: in flight_loop_callback Got 24 [WMKC ]SHA: in flight_loop_callback Got 24 [WMKC ]SHA: in flight_loop_callback
Interestingly, my parallel ExtPlane session returned:
RX 34 [ub laminar/B738/fmc1/Line01_M Vw==] ZiboFMC got [laminar/B738/fmc1/Line01_M|W] <--- the last 3 letters are missing
So it seems there’s some weirdness going on in ExtPlane, which is affecting my FMC.
Anyway, I removed the window drawing parts and just left my changes in the plugin, and the source can be found here.
I’ve not decided which way to go next; whether to use what I’ve learnt here and start debugging ExtPlane to see what’s going wrong; or … start writing my own plugin for the FMC. Writing my own plugin would be the more educational route, although I would probably end up replicating most of ExtPlane.
Checklist of stuff to learn to be able to write my own “dataref accessor” plugin:
- How to compile the plugin source on three platforms. Linux I have access to and familiarity with, and I can probably get a compiler going on Windows. But I don’t have access to a Mac (a VM Hackintosh maybe?). It’s a very interesting challenge though, especially to get a decent “workflow” going to build software this way.
- Running a separate thread to offload the bulk of the work out of X-Plane’s callbacks. Fortunately things are much simpler than they were 10-15 years ago, as C++11 now has threads. I’m assuming it would be easy to create cross-platform threaded code?
- Comms … I love BSD sockets on UNIX, but probably Boost::ASIO is a better cross-platform solution? I’m also curious about using the new Boost::Beast WebSockets library. It supports framing messages within the comms.
- Encoding … it would be more efficient to index the dataref names, like the X-PLane UDP API. And a lot more efficient to send binary data over the wire. But it’s also tempting to have a JSON encoded gateway to X-Plane’s internals (javascript dashboards?). Or perhaps I could support both binary and JSON?
Originally created with EverNote at 20180127T093712Z