Up till now, I've been using the ExtPlane plugin to integrate my 737 home cockpit components with the X-Plane Flight Simulator. ExtPlane is a third party plugin that gives networked devices access to X-Plane's DataRefs and Commands. With it, external devices can query the current state of the Flight Simulator, such as the current airspeed and elevation, or the present position of a switch. It can also be used to manipulate state within the Flight Simulator, such as to change the state of a particular switch.
ExtPlane works over a TCP network stream, and has commands to subscribe and unsubscribe to DataRefs, change the value of a dataaref and execute a command. The plugin makes of the X-Plane SDK to access X-Plane's internal data structures. The plugin is efficient in that DataRef values are only communicated to client's whenever the value changes.
However, because ExtPlane is a third-party plugin, it requires installation into the flight sim PC before it can be used. Clients also need to be configured with the IP address abd port number of ExtPlane in the flight simulator PC. Being a third-party product it also adds one more link in the chain where things can possibly go wrong -- something I experienced recently while trying to debug a problem.
X-Plane has a native network interface!
While browsing around I came across the fact that X-Plane has a built-in network interface. It's actually documented in the X-Plane folder itself, under Instructions/X-Plane SPECS from Austin/Sending Data to X-Plane.rtfd/TXT.rtf in X-Plane 11 or Instructions/Sending Data to X-Plane.rtfd/TXT.rtf in X-Plane 11. Unlike the ExtPlane plugin, this interface works over UDP. Based on the documentation, there are a number of messages supported by this Interface, of which the ones of interest to me are:
- BECN - Beacon broadcast. There is no equivalent feature for this in ExtPlane. Every running instance of X-Plane will broadcast it's IP address and command port number on your LAN in a BECN message, every second. With this, it is possible for devices on your LAN to "auto-discover" the IP and port number of the X-Plane server. It is also possible to auto-detect if there are multiple X-Plane servers running on your LAN, and offer the user a choice of which one they wish the device to connect to. It's a lot better than having the user manually configure the device with the IP and port number of the server, and helps a lot when you have more than one X-Plane PC. Note that this is a multicast datagram, so you will need to "subscribe" to the multicast address 188.8.131.52 port 49707.
- RREF - Request subscription to a DataRef. The client sends this message to X-Plane's command port, together with a frequency (in Hz) and an identifying number. X-Plane will then automatically send the client a response RREF message at the frequency specified containing the current value of that DataRef . To unsubscribe, you send the RREF again with a frequency of 0.
- DREF - Set the value of a DataRef. You send a DREF together with the value you want, and the name of the dataref.
- CMND - Gets X-Plane to execute a command. You send a CMND together with the name of the Command you want invoked, and X-Plane executes it.
There are other commands but these are all that I'm interested in. With this I can have my devices work directly with X-Plane just like that and not require a third-party plugin or any configuration. It is possible for the message formats to change with different releases of X-Plane, so you have to be aware of the version of the server and whether you can support the protocol it needs. The version of the protocol is announced in the BECN message and so far there are no differences between X-Plane 10 and X-Plane 11 for the messages above.
Although the document defines the datagrams in terms of C "structs", because the data is being sent over the network, I found it useful to declare my message buffers as a:
char buf [xxx];
or better still:
uint8_t buf [xxx];
And then fill the individual bytes with whatever I wanted to send, using offsets into the array. This is "safer" than using structs as the compiler often inserts padding bytes into the struct so that the fields align with the processors word alignment. I also used WireShark to sniff the datagrams on the network to confirm they were being sent exactly as I (or rather X-Plane) was expecting them to be.
The other point to note is that the X-Plane document defines the byte-ordering as what's used on an intel architecture (which is Little Endian) and not network byte-order (which is Big-Endian). Both the platforms I develop on (Linux on PC, Linux on Raspberry Pi) are Little Endian so I didn't have to do any conversions. If you're on some other platform however, this is something to watch out for. I've not included conversion in my code yet as I have no way to test it.
Here's a sample of the message formats as seen "on the wire":
BECN Beacon Broadcast (X-Plane Multicast)
- char  name = BECN\0
- uint8_t beacon_major_version = 01
- uint8_t beacon_minor_version = 01
- uint32_t application_host_id = 01
- uint32_t versionNumber = 01 00 00 00 (00000001 hex)
- uint16_t receivePort = 7A AE 01 00 (0001ae7a hex, 110202 decimal, aka 11.02.02)
- uint32_t role = 01 00 00 00 (00000001 hex)
- uint16_t port = 68 bf (bf68 hex, 49000 dec)
- char[ ] hostname = "pc-shahada"
RREF Subscription Request (Client to X-Plane)
- char  name = RREF\0
- uint32 freq = 01 00 00 00 (1 Hz)
- uint32 en = 05 00 00 00 (en = 5)
- char dref = "sim/aircraft/view/acf_descrip"
RREF Data (X-Plane to Client)
- char  name = RREF\0
- Value 1:
- uint32_t en = 00 00 00 00 (en = 0; subscribed as "sim/aircraft/view/acf_descrip")
- float value = 00 00 84 42 (66.0 dec, ascii "B")
- Value 2:
- uint32_t en = 01 00 00 00 (en = 1; subscribed as "sim/aircraft/view/acf_descrip")
- float value = 00 00 de 42 (111.0 dec, ascii "o")
- Value 3:
- uint32_t en = 02 00 00 00 (en = 2; subscribed as "sim/aircraft/view/acf_descrip")
- float value = 00 00 ca 42 (101.0 dec, ascii "e")
- Value 4:
- uint32_t en = 03 00 00 00 (en = 3; subscribed as "sim/aircraft/view/acf_descrip")
- float value = 00 00 d2 42 (105.0 dec, ascii "i")
- Value 5:
- uint32_t en = 04 00 00 00 (en = 4; subscribed as "sim/aircraft/view/acf_descrip")
- float value = 00 00 dc 42 (110.0 dec, ascii "n")
- Value 6:
- uint32_t en = 05 00 00 00 (en = 5; subscribed as "sim/aircraft/view/acf_descrip")
- flota value = 00 00 ce 42 (103.0 dec, ascii "g")
It should be noted that X-Plane can send many RREF messages to the client per second, depending on the frequency of the DataRefs requested, e.g. if you request dataref A at freq 4Hz, and dataref B at freq 2Hz, you'll get four datagrams per second, with 2 containing just A, and another 2 containing A and B. The number of DataRefs in a message is can be determined by the size of the message, taking each DataRef as 8 bytes. Because DataRefs are identified using the "en" field (a uint32_t) the client will have to maintain its own mapping of DataRef names to "en" when subscribing to them.
Its also important to bear in mind that the X-Plane UDP protocol sends all DataRef values as 4-byte single-precision floats. Even character DataRefs are sent over as floats (as in the example above)!
DREF (Client to X-Plane)
- char  name = DREF\0
- float value = 34 33 33 3F (0.7 dec, i.e. set throttle to 70%)
- char dref = "sim/multiplayer/controls/engine_throttle_request"
CMND (Client to X-Plane)
- char  name = CMND\0
- char cmnd = "sim/flight_controls/flaps_down"
I've made a C++11 library to encapsulate the code needed for a client to talk to X-Plane. Since my development is mostly centered around Linux on the PC and the Raspberry Pi, these are UNIX-ish libraries (btw: If you're developing on the ESP8266, check this out).
Since the code is quite short, I just plan to copy the *.cpp and *.h files into any projects I build. I might look into making this into a shared library .deb hosted on a repo in the future if there is demand.
I've created two different classes (1) XPlaneBeaconListener and (2) XPlaneUDPClient. They both spawn their own theads in the background so you will need to add "pthread" to your linker settings.
This is used to listen for X-Plane beacon signals. It launches it's own thread in the background to handle this. The library is implemented as a singleton and the thread is automatically created the first time it is accessed.
There are two ways determine the X-Plane servers that are available, via callback notification using registerNotificationCallback() or via polling the library using get().
Both methods make use of a XplaneBeaconListener::XPlaneServer class that contains all the details of a server.
registerNotificationCallback() will call a method or function you specify and pass a XplaneBeaconListener::XPlaneServer together with a flag to indicate if the Server has just started running or has just stopped. This is called from within the XPlaneBeaconListener's background thread, so you should return as soon as possible. Do not call get() from within the callback.
get() returns a list of XplaneBeaconListener::XPlaneServer of the current servers that are online.
Note that due to the nature of UDP broadcasts, it's not directly possible to determine when a server has started or stopped. We assume that receiving a beacon message for a server we haven't seen before an indicator that it has just started, and the lack of a beacon from an existing server for more than 30 seconds indicates that it has gone away. This means that if X-Plane gets "busy" -- such as when loading a new flight's sceneries -- the library may report the server shutting down and then reappearing again.
This provides an interface to the RREF, DREF and CMD messages. Start by creating an instance of XPlaneUDPClient, specifying the server's host and port numbers, as well as std::bind objects for callbacks that are called whenever a subscribed DataRef value changes. You can get the server's host and port details from the BECN message above.
Next you need to subscribe to the DataRefs that you are interested in, by calling subscribeDataRef and passing in the DataRef name and frequency desired. For array DataRefs, you should include the array index at the end, e.g. "DataRef[n]" for the individual values you are interested in.
A special case is for DataRefs that are character arrays. While you can subscribe to each character individually, you can also give a double array index at the end of the DataRefName, i.e. "DataRef[start][stop]". The library will then subscribe to each of the individual characters for you.
Note that a server need not be online when the subscription requests are made. The library will track the list of DataRefs subscribed, and automatically issue a subscription request when data for that DataRef has not been received in the last 3 seconds. This is useful also in situations where the Flight Simulator is restarted, as it effectively automatically resubscribes to DataRefs.
Although the DataRefs are updated many times a second, the library will notify the calling program only when a DataRef changes in value. For individual DataRefs this will be done via the receiverCallbackFloat() method, regardless of the original type of the DataRef (X-Plane converts them all to floats). For Character Array DataRefs, the library will assemble it back into a string and notify you when the last character in the array has been received, by sending a string containing the concatenation of all the character array DataRefs.
To change the value of any DataRef, call setDataRef(name, value). It should be thread safe (depending on the underlying socket library) so can be called from anywhere. Note that only one message is sent per setDataRef() call, if the server is not on at the time it is sent then the request is lost. This may not be a problem if you are frequently sending this value (e.g. for a throttle lever) but if it is something like a toggle switch, you may want to "refresh" the server with all the values whenever a server comes online or at regular intervals.
Commands can be executed using the sendCommand (command) method. The Network API doesn't distinguish between the phases of the command in the SDK, so sending a command starts, runs and stops it.
Get the Library
The latest source code to my library can be found at https://github.com/dotsha747/libXPlane-UDP-Client . It includes two test programs, one to monitor for beacons and the other an example of monitoring and modifying datarefs by moving the throttle repeatedly in and out.
Meanwhile, here's the test program in action ...