Interface Flight Gear using .Net and C#

Please, read the updated version here: http://www.matejtomcik.com/Public/Projects/FlightGear

This topic extends the previous post “FlightGear Simulator”. Here, you will learn how to write a C# application to interface Flight Gear simulator. The method is very simple.

As I described in the previous post, Flight Gear can communicate with the outside world using TCP or UDP sockets. To create your application that will intercept the output from Flight Gear, you have to:

  1. Create a protocol definition file to tell Flight Gear that we want it to output it’s internal variables or accept variables to alter the simulation. Such a file looks like this:

    <?xml version="1.0"?>
    <PropertyList>
      <generic>
        <output>
          <line_separator>#</line_separator>
          <var_separator>;</var_separator>
          <chunk>
            <name>altitude-ft</name>
            <type>float</type>
            <format>%f</format>
            <node>/position/altitude-ft</node>
          </chunk>
        </output>
      </generic>
    </PropertyList>

    The output datagram will contain only one variable, which is the altitude in feets. If you add more variables, they will be separated by a semicolon and the datagram will end with # character. For a complete list of available variables to input/output see the Property Tree/Reference. Then you place your xml file in data\Protocol folder.

  2. Create a UDP or TCP server on a localhost and start receiving datagrams. This can be achieved easily with the System.Net.Sockets.UdpClient class. Just choose a port number.
  3. Start the Flight Gear with additional parameters telling it to work with your protocol definition file and do variables input/output from/to the outside world. Such a command looks like this:
    fgfs.exe –fg-root=”..\..\data” –generic=socket,out,10,localhost,1234,udp,myprotocol
    Explanation:

    1. fg-root sets the root directory, this should be the same in most of the installation
    2. generic creates a pipe between Flight Gear and an outside world application. In this example, we told Flight Gear to create a socket output pipe, firing new datagrams 10 times per seconds to an UDP server at localhost port 1234 using myprotocol.xml file definition.
  4. Process the received datagrams according to the protocol definition file.
    Depending on the rate of incoming datagrams you may face a problem if a previously received datagram is not yet processed but another one is being received. So creating a non-blocking receiver is essential.

OR you can use my small framework (download it here) 🙂 It is based on a simple UDP non-blocking server which can be extended very easily by deriving a new class in which you will override just one method used to process every datagram.

Lets assume we have 4 output variables: /position/altitude-ft, /velocities/airspeed-kt, /position/latitude-deg, /position/longitude-deg. These will be sent in this format: altitude-ft;airspeed-kt;latitude-deg;longitude-deg#
Then your object will receive a datagram through the ProcessFrame(byte[] datagram) method where you split the datagram into variables and handle them according to your needs. An example class:

using System;
using System.Globalization;
using System.Net;
using System.Text;

namespace FlightGearInterface
{
    /// <summary>
    /// Example class which binds to a UDP socket and process incoming
    /// packets using a specific format
    /// </summary>
    /// <remarks>This listener expects the received data to be in the
    /// format specified in
    /// csharpinterface.xml</remarks>
    public class MyListener : UDPListener
    {
        private float altitude;
        private float airspeed;
        private double latitude;
        private double longitude;

        protected const float FeetsToMeters = 0.3048f;

        /// <summary>
        /// Initializes a new instance of MyListener
        /// </summary>
        /// <param name="endpoint">Local address to bind the
        /// listener to</param>
        /// <param name="nonBlocking">Indicates whether the UDP server
        /// waits for a frame to be processed before accepting
        /// next frames</param>
        public MyListener(IPEndPoint endpoint,
            bool nonBlocking = false) :
            base(endpoint, nonBlocking)
        {

        }

        #region Properties

        /// <summary>
        /// Gets the altitude in meters
        /// </summary>
        public float Altitude
        {
            get
            {
                return altitude * FeetsToMeters;
            }
        }

        /// <summary>
        /// Gets the airspeed in knots
        /// </summary>
        public float Airspeed
        {
            get
            {
                return airspeed;
            }
        }

        /// <summary>
        /// Gets the latitude in degrees
        /// </summary>
        public double Latitude
        {
            get
            {
                return latitude;
            }
        }

        /// <summary>
        /// Gets the longitude in degrees
        /// </summary>
        public double Longitude
        {
            get
            {
                return longitude;
            }
        }

        #endregion

        /// <summary>
        /// Process a single received frame
        /// </summary>
        /// <param name="datagram">UDP datagram</param>
        protected override void ProcessFrame(byte[] datagram)
        {
            // Convert received bytes into a string
            string receivedLine =
                Encoding.ASCII.GetString(datagram);
            // Remove last # character and split into
            // chunks using ; as delimiter
            string[] chunks = receivedLine.Substring(
                0, receivedLine.Length - 1).Split(';');

            // Parse chunks
            altitude = Convert.ToSingle(chunks[0],
                CultureInfo.InvariantCulture);
            airspeed = Convert.ToSingle(chunks[1],
                CultureInfo.InvariantCulture);
            latitude = Convert.ToDouble(chunks[2],
                CultureInfo.InvariantCulture);
            longitude = Convert.ToDouble(chunks[3],
                CultureInfo.InvariantCulture);
        }
    }
}

And here is the example program class:

using System;
using System.Diagnostics;
using System.Net;
using System.Threading;

namespace FlightGearInterface
{
    /// <summary>
    /// Program
    /// </summary>
    public class Program
    {
        private static uint frameCounter;

        /// <summary>
        /// Program entry
        /// </summary>
        /// <param name="args"></param>
        public static void Main(string[] args)
        {
            Console.WriteLine("Terminate with Ctrl+C\n");

            // Start FlightGear if it is not already running
            RunFlightGear();

            // Create UDP listener at 0.0.0.0:1234
            MyListener listener = new MyListener(new IPEndPoint(
                IPAddress.Any, 1234));

            // Create a manual reset event (just for keeping the console
            // window shown until the user press the combination of Ctrl+C)
            ManualResetEvent exitEvent = new ManualResetEvent(false);
            Console.CancelKeyPress += new ConsoleCancelEventHandler(
                delegate(object sender, ConsoleCancelEventArgs e)
            {
                // Dispose UDP listener (closes UDP server)
                listener.Dispose();
                // Signal that we are ready to exit the program
                exitEvent.Set();
            });

            // Register an event handler which will be called after a frame
            // is processed
            listener.FrameProcessed += listener_FrameProcessed;

            // Wait for user pressing Ctrl+C
            exitEvent.WaitOne();
        }

        /// <summary>
        /// Frame processed
        /// </summary>
        /// <param name="sender"></param>
        private static void listener_FrameProcessed(UDPListener sender)
        {
            // [frame number] Altitude (m)  Airspeed (knots)
            Console.Write("\r[{2,6}] Alt: {0:0.00}m IAS: {1:0.00}kt\t\t",
                ((MyListener)sender).Altitude,
                ((MyListener)sender).Airspeed, frameCounter++);
        }

        /// <summary>
        /// Starts FlightGear if it is not already running
        /// </summary>
        private static void RunFlightGear()
        {
            // Check whether FlightGear is already running
            Process[] processes = Process.GetProcessesByName("fgfs");
            if (processes == null || processes.Length <= 0)
            {
                // Create process start information
                ProcessStartInfo startInfo =
                    new ProcessStartInfo(
                    @"D:\Hry\FlightGear\bin\Win32\fgfs.exe",
                    "--fg-root=\"..\\..\\data\" --generic=" +
                    "socket,out,10,localhost,1234,udp,csharpinterface");
                startInfo.WorkingDirectory =
                    @"D:\Hry\FlightGear\bin\Win32";

                // Start FlightGear
                Process.Start(startInfo);
            }
        }
    }
}
Reklamy

Pridaj komentár

Zadajte svoje údaje, alebo kliknite na ikonu pre prihlásenie:

WordPress.com Logo

Na komentovanie používate váš WordPress.com účet. Odhlásiť sa / Zmeniť )

Twitter picture

Na komentovanie používate váš Twitter účet. Odhlásiť sa / Zmeniť )

Facebook photo

Na komentovanie používate váš Facebook účet. Odhlásiť sa / Zmeniť )

Google+ photo

Na komentovanie používate váš Google+ účet. Odhlásiť sa / Zmeniť )

Connecting to %s