Property Tree/Sockets

From FlightGear wiki
Revision as of 07:33, 14 July 2020 by Kaklik (talk | contribs) (→‎UAV Visualization: add link to real use case.)
Jump to navigation Jump to search

It should be pretty straightforward to use FG to playback a real flight. With FG you can slave multiple instances to one master instance of FG in real time. Slaving a copy of FG to a recorded data stream should be also very similar.

I have instances of FG talking to other copies of FG as well as an external FDM talking to FG using the same structure. There can be packing differences between compilers, but we were pretty careful with the current structure (all values are 4 or 8 bytes) and we don't know of any compilers that pack the structure differently from any other compilers.

You can overwrite most properties using a custom protocol, however you have to ensure that the data you are trying to overwrite isn't being written to by any other subsystems or parts of FlightGear.

This could for example be the FDM, which uses so called "tied properties" - basically, the properties tied by the FDM are not owned by the property tree, they are owned by the FDM and the property tree merely holds references/pointers to the FDM properties.

In a sense, you have to differentiate between input and output properties.

The easiest thing to avoid such problems is to completely disable the corresponding subsystem. In the case of the FDM this can be done by configuring the --fdm=null switch. So that no FDM is instantiated, that might cause conflicts.

On the other hand there are a number of subsystems that cannot currently be directly disabled, these would need to be patched in the source code.

Also, any property which is updated in a hard coded fashion could possibly conflict with your UDP packets. For example, some properties are updated each frame, and while overwriting these properties will not yield an error or warning, your updates would be invalidated due to these hard coded updates in the main loop.


The following fgfs parameters will create the control sockets:
--native-ctrls=socket,in,10,127.0.0.1,5600,udp
--native-ctrls=socket,out,10,127.0.0.1,5700,udp

You will likely also need FDM data:
--native-fdm=socket,out,10,127.0.0.1,5500,udp

Getting started

One big question is what language/platform are you connecting to?

The easiest thing to do would be to write the other application in C/C++ and just use the exact same structure. If you look at the socket calls you will see that you provide a buffer and a length and it will fill it in with the network data.

You may have to worry about endianess if you are communicating between two different architectures, but in this case, the code converts to/from network byte order so that shouldn't be a problem.

If you want to know the size of different structures such as bool or int or double, there is a function in C/C++ called sizeof(). So you could do something like "cout << sizeof(bool) << endl;"


FlightGear vs. WideFS

FlightGear can do all that already. You can set up one copy to be a master, and any number of additional copies to be slaves. For each display channel you can specify view offset direction and fov. You can set up anything from a simple 3 monitor display system to some huge convoluted mess if you want to gang up 20 machines.

Hardware in the loop

Our solution has been to use the FGNetCtrls interface to FG and write a gateway application that would interface our UAV flight control computer (via serial port) to FG (via TCP). This allows us to do hardware-in-the-loop testing of the autopilot system. If preferable, you could also implement the flight control laws in a PC application and "fly" the FG model via a TCP connection.

Actually, flightgear has proven to be a versatile tool for our project. It is also good as a visualization system (either live during flight or for post-mission replay). Once you figure it out, communication with FG is pretty easy. I've just created a C# class to handle all I/O to FG, and can drop it in where needed.


UAV Visualization

This use case is utilized in Paparazzi autopilot


And if you are using FlightGear as a visualization system for your UAV (either live or replaying the data) you can turn on multiplayer mode and inject yourself into the multiplayer system. I'm doing that right now. If you want to see my university's UAV in action, go to mpmap01.flightgear.org right now and click the checkbox next to the Rascal-1 call sign. I'm looping the data and will probably continue looping it for the rest of the day. We don't have a wireless internet connection that available at our flying field, but someday if we are able to rig something up, we'll be able to inject our live flight data into the multiplayer system which and others could come fly [virtually] with us. That would be kind of a neat trick.

Using Proprietary/External FDMs

Take a look at net_fdm.* and native_ctrls.* in the FlightGear/src/Network directory. Those are the one you want to use.

Also look at Docs/REAME.IO

You can turn off the built in flight dynamics engine with the --fdm=null option. Then specify the --native-fdm= and --native-ctrls= options with appropriate parameters. That sets up FlightGear to receive data from an external source. Then all you have to do is look at the UDP packet structure and send data packets from your external software. There maybe some trickier details if you've never done socket communication before, but it's been done many times and there are many examples to look at including the FlightGear code itself. You can configure one "master" copy of FlightGear to control one (or more) other copies of FlightGear in this same way -- this is one way to achieve a multi-screen cockpit display if you don't want to plug multiple monitors into a single computer.

Look in net_fdm.hxx for the variable type and units, as in radians and meters.

The goal is to also include in that subdir some stand along FDM wrapper apps that interface to FlightGear via the ExternalNet interface. These would act as examples for people interested in integrating other FDM codes this way.

I also removed the External fdm because it is replaced by a better named NullFDM fdm. The --fdm=external option is stull supported for backwards compatibility but maps to --fdm=null (which does the same thing.)

So the only command line change would be to go from

     --native=socket,in,30,,5500,udp --fdm=external

to

     --native=socket,in,30,,5500,udp --fdm=null

--fdm=external still works even though --fdm=null is now the 'official' way to do it.

Some of the benefits of running the FDM portion as a standalone app separate from flight gear are:

  • you can kill and restart the fdm portion without restarting FG.
  • you can change/recompile/rerun the fdm portion without restarting FG.
  • when developing the fdm portion, you don't need to spend the overhead of compiling/linking with FG and starting FG from scratch.
  • you can interface non-GPL'd flight dynamics code via this mechanism.
  • you can run the FDM on a separate machine/OS, separate CPU, you could even develop it in a different computer language.

Almost all if not all of the FGNetFDM values are getting filled in by the fdm. But it is likely that not all fdm's will generate entirely the same output fields, and not all of them will care about entirely the same inputs.

Out of necessity this structure is extended as new needs are discovered. We include a version number in the structure so software on either end can verify they are passing the same version of the contents.

The included source code that can take an instance of NetFDM and translate it into FG properties as well as the code that can build an instance of NetFDM from the current state of the property tree can optionally do it's work in network byte order.

When the structure does change, it is literally trivial to upgrade the other end. Copy over the new structure, see if you care about any of the new fields (usually not) and recompile your software.

If they want the ultimate in flexibility they should use Eric's "generic" output facility because there you can define exactly what output you want via an xml config file and it only changes when you want it to change.

FlightGear as a Scenery Generator

FlightGear has exactly what you are looking for.

Look in src/Network/net_fdm.hxx ... this defines a structure that you can pass to FG. If you run FG with the --fdm=null option you will disable the internal flight dynamics and you will get whatever you pass in with the net_fdm.hxx structure.

Additionally we have a net_ctrls.hxx structure which you can also pass to FG. That will enable the controls (ailerons, flaps, gear, etc.) to be animated in the external views.

FlightGear has been used as an image generator on an FAA Level 3 FTD certified simulator. I've seen people post questions who are also working on leveraging FG as an image generator in one way or another ... either interfacing it to an existing simulator, or trying to import the FG scenery into their existing image generation software, or trying to import their existing image generation scenery back into FG.

There are a couple things to keep in mind that you will run into soon enough.

  • You need the airport runway and navaid database to match between FlightGear and PS1. If they don't, you are going to be perfectly lined up on your approach in PS1, and may pop out of the clouds to find yourself severely misaligned with the runway.
  • You need PS1 and FG to agree on the ground elevation. FG can be configured to send export the elevation of the ground in the FG world at the current spot, but you'd need to find a way to import that back into PS1. If you can pull that off, then you can properly taxi on FG's non-flat runways, you won't be able to fly underground, you won't crash into mystery terrain that is in PS1, but not if FG, etc. etc.
  • You will want to pass along weather parameters to FG so that the wind socks are blowing the right way, the cloud layers are in the right place, etc. etc. And if you use multiple displays, you want them all configured the same way and synced with time so they all draw the sun/moon/ stars in the same place, and have the same shading, coloring, and lighting of the scene.
  • On good hardware, FG can run at 60+ fps. If I recall, PS1 updates on the DOS interrupt which is 18.2 hz I think. You will get "jittery" video if you don't sync FG exactly to the PS1 clock, or some multiple of that. I'm not sure if it's possible to run your monitor refresh at an exact multiple of 18.2 so you might just have to live with jittery video which you probably don't mind if you are using MSFS as your reference point. :-)

So for the most part, it is all very doable, and you should be able to get something up and running very quickly, especially if you have some socket networking experience, but there are some things that you'll need to consider and handle to really make it work well.

FGNetFDM & FGNetCtrl

What we did with the FGNetFDM and FGNetCtrls structures was to write separate header files outside the scope of the FlightGear project and put these into the public domain.

There is no problem with including these files in a GPL program such as FlightGear, and also no problem including these files in some other proprietary software.

This is not to be seen as an attempt to circumvent the GPL. It's just a convenience to make communication easier and make FG usable for a larger set of tasks.

It's a good idea to think in terms of a "data" channel and a "command" channel. The data channel would be the NetFDM structure that would get blasted over via UDP at a fast rate (i.e. 60 hz) If you drop a packet, don't spend time trying to resend old data, just send the new data and keep going.

The "command" channel (props/telnet) is a lower bandwidth, high reliablity channel. You are guaranteed that every message gets to the reciever once (and only once.) This is great for setting up weather conditions, time of day, camera parameters, and anything else that might have an impact on the visuals. We have a convenient "telnet" interface to all the internal properties and built in commands. Anything you can set from the keyboard, or mouse, or gui, you can do from a remote program or script. It's much lower bandwidth, but very convenient.


You can use the net-ctrls and native-ctrl structures in the Network directory to modify the properties to set radio/nav frequencies but that takes a bit of work to hack the code. Some of which may not be applicable for general use. Approach would be to add the frequency variables to the network packet and modify the source to load the values from the received packet into the appropriate properties in FG. Then you'll need to create the network program on the "simulated radio stack" client to build the packet, create the socket connection, and establish network communications. At least that's the approach I've taken for my 747 project. If you're modestly fluent in C++, the existing code serves as a good example and base to expand to meet your needs.

One big problem ;-) The network packet loads at whatever rate you select via the socket options and overwrites any and all the property values called out in the net-ctrls data structure and that includes all the controls for flying. Which is fine if you plan to also control FG with an external machine. I suppose you could "turn off" updating those properties you don't want to update in FGNetCtrls2Props, but that's a messy hack.

I'm assuming you don't need a lot of bandwidth. There are other options less envasive and unless you plan to expand your hardware at some future date those might meet your needs

The two native protocols compliment each other. Originally they were designed to communicate with an external FDM. FlightGear would send native-ctrls to the remote FDM module, and it would reply with native-fdm data.

You can sync up visuals by only using native-fdm= since that contains all the position and orientation information.

If you also want to syncronize the control inputs (i.e. for animating the external model) you can add that. This comes in really handy if you are running the wright flyer on a 5 projector wrap around visual system. You want to see that big elevator waggling in front of you as you struggle to stay alive for just a few more seconds.

The slaves should use --fdm=null so they don't try to compute their own positional information.

It really depends on what you are trying to do.

I think you'll find most of the information you need in

  • src/Network/net_fdm.hxx

and (if you also send control movements)

  • src/Network/net_ctrls.hxx

but some processing (like byte order swapping) most likely is in

  • src/Network/native_fdm.{hxx,cxx}

and

  • src/Network/native_ctrls.{hxx,cxx}

By default, everything that is transferred is done so in network format (big-endian). Sending data in any other format is asking for trouble (i.e., how can I send data from my PowerPC to my x86 box if it is not in network format). Look at source/src/Network/native_ctrls.cxx to see how the data is processed. The sizes are:

  • Boolean = 8-bits
  • Integer = 32-bits
  • Float = 32-bits
  • Double = 64-bits

Don't forget data aligment. The C/C++ compiler will pad data so that is alighed per the processor's architecture. This means that an array of three bytes that is followed by an integer will *MOST LIKELY* have a hidden fourth byte applied after the array and before the integer. The compiler will align data on its native boundary (i.e., 32-bit numbers will start on a 32-bit boundary).

Most compilers have a set of switches you can use to control structure padding and alignment. But indeed, there's nothing in the spec that says what the proper alignment should be.

FWIW, every compiler I know stores each element in order, and pads it up to its "natural" alignment. So this struct takes 8 bytes in memory on all platforms I know:

struct { int8_t a; int8_t b; int32_t c; }

But this one takes at least 12 (word-size padding is added at the end on most or all compiler):

struct { int8_t a; int32_t b; int8_t c; }

So long as the structure honors those rules, you should be more or less portable. Other options are, obviously, to store everything as a int32_t and do the bit packing yourself. You'll need to do this in any case if you want endian compatibility. This is one of the many reasons that dumping structures from memory to I/O is considered problematic.

In general, You must never send a binary stucture across a network. Even if you get the sizes correct, you'll have endian problems when running different architectures (PC vs Mac). You must manually ship the data bytes in a defined order and reconstruct it at the other end. At that point, it doesn't matter what the padding or for different compilers (or the order) is because you won't be sending the padding - just the data.

Never block transfer a structure by providing a pointer and size, there is simply no way for that to work cross-platform.

If deciding to use a binary interface, I'd recommend using XDR (RFC 1014) throughout (already used by the MP system).

It's not too far away from what FG currently has (for the more common platforms at least) but has the advantage of being well-defined.

<stdint.h> is indeed a standard C header but *not* in C++ (though it might be included in the future under the name <cstdint>). AFAIK even if <stdint.h> defines the number of bits for the individual types, it does *not* imply anything about where the individual bits live in memory. Others have brought up issues like alignment, etc.

The net_fdm.hxx and net_ctrls.hxx files are from Flightgear and define two data structures that can be sent and received by fgfs. In your program you can receive the FGNetCtrls structure, change the parameters you want to control, and send it back.


Native Socket

Before we interface with FlightGear on a socket, consider this demonstration using the native protocol. TODO: explain native protocol

 --native=socket,direction,hz,machine,port,type
   direction = in, out or bi
   hz = number of times per second
   machine = machine name or ip address if client (leave empty if server)
   port = port, leave empty to let system choose
   type = tcp or udp
eg
 --native=socket,out,10,,5444,udp
                        ^ no machine so binds to localhost
 --native=socket,in,1,192.5.22.33 ,6789,tcp
 //**Important**. space = fail    ^                                 

An example of slaving two instances of flight gear together on seperate machines

   fgfs1:  --native=socket,out,30,seattle.com,5500,udp
   fgfs2:  --native=socket,in,30,tolouse.net,5500,udp --fdm=external

   This instructs the first copy of fgfs to send UDP packets in the
   native format to a machine called seattle.com on port 5500.

   The second copy of fgfs will accept UDP packets (from anywhere) on port 5500.  
   Note the additional --fdm=external option.  This tells the second copy of fgfs to not run 
   the normal flight model, but instead set the FDM values based on an external source, the network in this case.