Generic protocol

From FlightGear wiki
Jump to navigation Jump to search

FlightGear supports multiple concurrent IO connections.

You could use the FGNetFDM (--native) protocol or the --generic protocol (which allows you to design custom packets with the specific fields you choose.) Both of these can send the data out as UDP packets which then can be read by your external program using standard socket communication.

This same basic mechanism also would permit your external program to send control commands so if some day you want to do some sort of hardware or software in the loop testing of your UAV controller, that is also a possibility.

There is a slightly complex, but pretty flexible mechanism for specifying an IO channel, what type of communication will get sent over it, which direction, and at what rate. Look for a file called README.IO that briefly explains much of this.

In addition, there is a "generic" protocol where you can create an XML file specifying exactly what data values you wish to send across your IO channel. We don't have generic "USB" support, but if you have a Serial -> USB device that looks like a COM port on the computer side, then that should work without any problem ... just be careful which COM port your USB device gets assigned to.

There is the native_fdm protocol to control FlightGear using an external FDM and there is the generic protocol that uses the XML configuration files.

The generic communication protocol for FlightGear provides a powerful way of adding a simple ASCII based or binary input/output protocol, just by defining an XML encoded configuration file and placing it in the $FG ROOT/Protocol/ directory.

The generic protocol can be easily set up for both input and output. And it supports not just a single medium, but can be easily made to use files, FIFOs, sockets etc. The generic protocol is really good for things like exporting to a delimited ASCII file for import into spreadsheet for instance.

I think it makes sense (when dealing with live data) for the generic protocol to enter a while loop and read data until there is no more available. That way if the sender is sending at a higher rate or FlightGear gets behind for any reason, FlightGear will get caught up each iteration. This obviously does not work well for file input though, so you'd have to differentiate.

XML File Layout

A protocol file can contain either or both of <input> and <output> definition blocks. Which one is used depends on how the protocol is called. The following example would only use the <output> definitions block.

 --generic=file,out,1,/tmp/data.xml,myproto

If you're using a serial port under Windows, you must use a special escape sequence for COM port numbers higher than COM9, for example:

--generic=\\.\COM10,out,1,/tmp/data.xml,myproto

More detail can be found in this Microsoft KB article: https://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx

Overview of the XML file

 <?xml version="1.0"?>
 <PropertyList>
    <generic>
 
        <output>
            <binary_mode>false</binary_mode>
            <line_separator></line_separator>
            <var_separator></var_separator>
            <preamble></preamble>
            <postamble></postamble>
 
            <chunk>
                <!-- First output chunk definition -->
            </chunk>
 
            <chunk>
                <!-- Next output chunk definition etc... -->
            </chunk>
        </output>
 
        <input>
            <line_separator></line_separator>
            <var_separator></var_separator>
 
            <chunk>
                <!-- First input chunk definition -->
            </chunk>
 
            <chunk>
                <!-- Next input chunk definition etc... -->
            </chunk>
        </input>
 
    </generic>
 </PropertyList>

Input/Output Parameters

Both <input> and <input> blocks can contain information about the data mode (ascii/binary) and about separators between fields and data sets, as well as a list of <chunk>s. Each <chunk> defines a property that should be written (and how), or a variable and which property it should be written to.

ASCII protocol parameters

Note that some parameters only can be used for output.

Parameter tag Out In Type Default Purpose
<preamble> Yes No String "" File header put on top of the file
<postamble> Yes No String "" File footer put at the end of the file
<binary_mode> Yes Yes Boolean False Indicates if binary mode is to be used instead of ASCII
<var_separator> Yes Yes String "" Field separator
<line_separator> Yes Yes String "" Separator between data sets
  • <var_separator> is put between each of the properties.
  • <line_separator> is put at the end of each data set.

Both can contain arbitrary strings or one of the following keywords:

Keyword Special character
newline \n
tab \t
formfeed \f
carriagereturn \r
verticaltab \v

Typical use could be:

 <var_separator>tab</var_separator>
 <line_separator>newline</var_separator>

or

 <var_separator>\t</var_separator>
 <line_separator>\r\n</line_separator>

Binary protocol parameters

To enable binary mode, simply include a <binary_mode>true</binary_mode> tag in your XML file. The format of the binary output is tightly packed, with 1 byte for bool, 4 bytes for int, and 8 bytes for double. At this time, strings are not supported. A configurable footer at the end of each "line" or packet of binary output can be added using the <binary_footer> tag. Options include the length of the packet, a magic number to simplify decoding. Examples:

  <binary_footer>magic,0x12345678</binary_footer>
  <binary_footer>length</binary_footer>
  <binary_footer>none</binary_footer>                 <!-- default -->

In addition, you can specify endianness of the data sent. This is done with the <byte_order> flag. The two options are network (big endian) and host.

  <byte_order>host</byte_order>
  <byte_order>network</byte_order>        <!-- default -->

If your generic protocol file specifies

    <binary_mode>true</binary_mode>
    <byte_order>network</byte_order>


your receiver (or sender) code also have to use that encoding convention. The encoding used by the generic protocol is independent of the output channel you choose (file, TCP socket, UDP socket, serial port or whatever).

Also with sending/receiving UDP packets, you need to use different ports for the sending and receiving channels.

If you are unsure about what encoding FG uses for binary data src/Network/generic.cxx is the place to find out. It is pretty much standard for the basic types as far as I know, though.

Network order is MSB first (as is host order if your system is big endian but that is not so common these days). If you use host order and both your systems have the same endianness (and you only care about your use case) you don't have to worry about this.

If you don't specify byte order, host order will be used. Anything x86 is very likely to use LSB order (a PC certainly is).

Variable Parameters - <chunk> spec

Both <input> and <output> block can contain a list of <chunk> specs, each of which describes the properties of on variable to write/read.

<name>

For ease of use and not transferred (like a notes tag)

<node>

The property tree node which provides the data

<type>

The value type which is needed for formatting, one of string, float, bool, int (default: int). Its recommended that this tag is present otherwise spurious results can occur.

<format>

ASCII protocol only, not used or needed in binary mode. Defines the actual piece of text which should be sent. it can include "printf" style formatting options like:

Formatting option Type
%s String
%d Integer (default)
%f Float

Note: the type attribute tells the property tree how to store a value internally, using the most appropriate data type saves memory and reduces conversion overhead (bool, int, float, double, string). Using the printf-style format strings in a generic protocol XML file, merely tells the I/O system how to encode/decode a property - so that it can be saved/transmitted and restored properly. Using the wrong type and/or format string in a binary protocol, would cause wrong data to be transmitted/processed by FG.

Basically, each type specifier tells FG how long each data type is in memory (bits-wise). For example, a boolean value is just 0 or 1 - so it only requires a single bit. A byte, on the other hand, requires 8 bits - and an integer (int) 4 bytes (32 bit).

So telling FG that something is a bit, will cause the data to be truncated to just a bit - which is what the format specifiers are all about: you can affect the re-interpretation of values from the property tree. In addition, there's the concept of signed-ness, i.e. numbers being positive or negative, or numbers having a mantissa - these affect how the values are stored in memory, and accordingly, the receiver must know how a value was encoded, to transform it back to what you want it to be.

<factor>

An optional multiplication factor which can be used for unit conversion, for example, radians to degrees.

<offset>

An optional offset which can be used for unit conversion, for example, degrees Celcius to degrees Fahrenheit.

<format>

Chunks can also consist of a single constant <format>, like in:

 <format>Data Section</format>

Serial I/O

You can assign any number you want to a COM port when it is driven by a USB-to-COM or Ethernet-to-COM adapter. Simply go to the Device Manager and set it between 1 and 255.

There are a lot of make of these kind of device. Some are good, others not so ...

Note  Windows:To specify a COM port number greater than 9, use the following syntax: \\.\COM10. This syntax works for all port numbers and hardware that allows COM port numbers to be specified.

it doesn't appear that the "generic" interface code is setup to transmit and receive at the same time. You can't open up the same device twice, so you two command line options won't work either. As far as I can tell this will require some code modifications if you want to use direct serial communication.

Another option might be to write a thin glue layer that talks to FlightGear over the network, and talks to your hardware over a serial port and then does all the appropriate data translation as required.


The good way to verify if the transmission of float values work is to print the value in your receiver and compare that to the value of the property you transmit in FG. Btw. if your generic protocol is set to use network byte order (endianness MSB) you have to take that into account when unpacking the float value.

Examples

Example 1

Writes log of this form:

V=16
H=3.590505
P=3.59
V=12
H=3.589020
P=3.59
 <?xml version="1.0"?> 
 <PropertyList>
 <generic>
 
    <output>
      <line_separator>newline</line_separator>
      <var_separator>newline</var_separator>
      <binary_mode>false</binary_mode>
 
      <chunk>
        <name>speed</name>
        <format>V=%d</format>
        <node>/velocities/airspeed-kt</node>
      </chunk>
 
      <chunk>
        <name>heading (rad)</name>
        <format>H=%.6f</format>
        <type>float</type>
        <node>/orientation/heading-deg</node>
        <factor>0.0174532925199433</factor>  <!-- degrees to radians -->
      </chunk>
 
      <chunk>
        <name>pitch angle (deg)</name>
        <format>P=%03.2f</format>
        <node>/orientation/pitch-deg</node>
      </chunk>
   </output>
 
 </generic>
 </PropertyList>

Writing data in XML format

Assuming the file is called $FG_ROOT/Protocol/xmltest.xml, then it could be used as

fgfs --generic=file,out,1,/tmp/data.xml,xmltest
 <?xml version="1.0"?>
 <PropertyList>
  <generic>
    <output>
      <binary_mode>false</binary_mode>
      <var_separator>\n</var_separator>
      <line_separator>\n</line_separator>
      <preamble>&lt;?xml version="1.0"?&gt;\n\n&lt;data&gt;\n</preamble>
      <postamble>&lt;/data&gt;\n</postamble>
 
      <chunk>
        <format>\t&lt;set&gt;</format>
      </chunk>
 
      <chunk>
        <node>/position/altitude-ft</node>
        <type>float</type>
        <format>\t\t&lt;altitude-ft&gt;%.8f&lt;/altitude-ft&gt;</format>
      </chunk>

For more examples, see mailing list thread.

      <chunk>
        <node>/velocities/airspeed-kt</node>
        <type>float</type>
        <format>\t\t&lt;airspeed-kt&gt;%.8f&lt;/airspeed-kt&gt;</format>
      </chunk>
 
      <chunk>
        <format>\t&lt;/set&gt;</format>
      </chunk>
    </output>
 </generic>
 </PropertyList>

Analyzing the resulting binary packet format

A utility called generic-protocol-analyse can be found under FlightGear/utils/xmlgrep which can be used to analyze the resulting data packet for the binary protocol. The output would be something like:

bintest.xml
Generic binary output protocol packet description:

 pos | size |  type  | factor     | description
-----|------|--------|------------|------------------------
   0 |    4 |    int |            | indicated speed (kt)
   4 |    4 |  float |            | pitch att (deg)
   8 |    4 |  float |            | magnetic heading (deg)
  12 |    4 |    int |            | outside air temperarure (degF)
  16 |    1 |   bool |            | autocoord

total package size: 17 bytes

Generic Protocol

FlightGear has an inbuilt "generic" protocol to push and pull data. (todo)

To interface with FlightGear on a socket for example, the following steps need to be taken:

  1. Establish which "properties" you want to appear "on the wire".
  2. Create a my_protocol.xml file containing those properties.
  3. Start FlightGear with the generic protocol option using my_protocol sent through the network socket.

For this simple example say were interested in heading and altitude, and we want to "listen" ie output to a socket at port 6789, UDP and receiving 10 times a second in the format:

alt\thead\n 
// ie altitude - tab - heading - newline

The first step is establishing the nodes, in this example

/position/altitude-agl-ft  eg (22.4713923)
/orientation/heading-deg  eg (297.966325)

Next create a file my_protocol.xml. This needs to be located at

$FG_ROOT/Protocol/my_protocol.xml

Edit the my_protocol.xml file and write in it:

 <?xml version="1.0"?>
 <PropertyList>
 <generic>
    <output>
        <line_separator>newline</line_separator>
        <var_separator>tab</var_separator>
 
        <chunk>
           <node>/position/altitude-agl-ft</node>
           <name>altitude to go</name>
           <type>float</type>
           <format>%03.2f</format>
         </chunk>
 
        <chunk>
           <node>/orientation/heading-deg</node>
           <name>Heading</name>
           <type>float</type>
           <format>%03.3f</format>
         </chunk>
 
    </output>
  </generic>
 </PropertyList>
  • The output tag indicates the protocol for output. The same "protocol" file can contain an "input" section, absent in this example (more later).
  • line_seperator - the end of line (todo - is this \r\n?)
  • var_seperator - the delimiter for the properties, this could be "|", "###", any string.
  • The two chunk blocks contain our data. Note that these appear in the order presented within the XML file.
    • node - the node of data we want
    • name - it there for a reference, its not transmitted and note necessary (like a note)
    • type - the way this xml is to interpret the value. This is quite important tag. For example if its not there or a string, then "floats" go wild and end up being a string of 32 characters with two decimal places!
    • format - same syntax as printf function in C/C++
    • There's more that can be done {todo - link to a full blown example/reference}

We can now start FlightGear and listen with:

fgfs --generic=socket,out,10,localhost,6789,udp,my_protocol

[todo] - link here to the tutorial

Alternative start using Nasal

It is also possible to start and stop from within the Nasal console. Start the Nasal console from the Debug menu and type the following (modify accordingly) and click Execute:

fgcommand("add-io-channel", {"config": "generic,socket,out,10,localhost,6789,udp,my_protocol", "name": "test"});

Name is optional but can be used to stop the connection using:

fgcommand("remove-io-channel", {"name": "test"});


Related content

Wiki articles

Readme files

Source code