Thanks Erik,

Here is the patch. Let me know what needs to be changed, fixed, or
refactored before it can go into CVS. Note that at this point, the
binary protocol is output only.

Also attached is a really simple protocol to test the patch
(bintest.xml). Run FGFS with:
$ fgfs ... --generic=file,out,24,bintest_out,bintest

As well, there is a Python script (sorry, the script is a horribly dirty
hack) to decode and print the results. Execute it with:
$ python test_decode.py

To do list for the generic binary support:
* use ntohl() etc to make the protocol independent of host endianess.
* add CRC checksum as a possible packet footer
* add binary input support
* test thoroughly

Cheers,
Hugo Vincent


On Mon, 2006-04-24 at 09:59 +0200, Erik Hofman wrote:
> Hugo Vincent wrote:
> 
> > I also updated OpenGC for this change and made a OpenGC.xml protocol
> > file, but it appears the OpenGC project is more or less dead. Also,
> > Network/opengc.cxx is a bit of a mess and seemingly unmaintained; it
> > might be nice to get rid of it and replace it with a XML file at some
> > stage. One approach is to directly replace opengc.cxx with an XML file
> > that emulates the structure of the ogcFGData class, - however different
> > compilers probably pad the variables in the class differently (for
> > alignment/efficiency), so a patch to OpenGC does need to be made. 
> > 
> > If anyone wants the generic binary mode patch, or better yet, wants to
> > add it into the main FlightGear repository, reply (to the list).
> > 
> > Also, if anyone else has uses or requirements for binary protocols, let
> > discuss them here, so that we can make generic binary support as broad
> > and applicable as possible.
> 
> I think it would be valuable, exactly for the reason mentioned above.
> 
> Erik
> 
> 

Attachment: bintest.xml
Description: application/xml

Index: Protocol/README.Protocol
===================================================================
RCS file: /var/cvs/FlightGear-0.9/data/Protocol/README.Protocol,v
retrieving revision 1.2
diff -u -p -r1.2 README.Protocol
--- Protocol/README.Protocol	22 Jun 2003 08:02:28 -0000	1.2
+++ Protocol/README.Protocol	25 Apr 2006 01:19:39 -0000
@@ -1,5 +1,5 @@
 The generic communication protocol for FlightGear provides a powerfull way
-of adding a simple ASCII based output only protocol, just by defining an
+of adding a simple ASCII based or binary protocol, just by defining an
 XML encoded configuration file.
 
 The definition of the protocol consists of variable separators, line separators,
@@ -16,8 +16,9 @@ Each chunck defines:
 			%s	string
 			%i	integer	(default)
 			%f	float
+		(not used or needed in binary mode)
 
-<factor>	an optionale multiplication factor which can be used for
+<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 Celsius to degrees Fahrenheit).
@@ -44,6 +45,16 @@ any other charachters just need to be ad
 The var_separator is placed between each variable, while the line_separator is
 placed at the end of each lot of variables.
 
+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 -->
 
 A simple protocol configuration file then could look something like the
 following:
@@ -57,6 +68,7 @@ following:
   <output>
    <line_separator>newline</line_separator>
    <var_separator>newline</var_separator>
+   <binary_mode>false</binary_mode>
 
    <chunk>
     <name>speed</name>
Index: src/Network/generic.cxx
===================================================================
RCS file: /var/cvs/FlightGear-0.9/source/src/Network/generic.cxx,v
retrieving revision 1.17
diff -u -p -r1.17 generic.cxx
--- src/Network/generic.cxx	9 Mar 2006 21:41:51 -0000	1.17
+++ src/Network/generic.cxx	25 Apr 2006 01:41:26 -0000
@@ -79,51 +79,99 @@ FGGeneric::~FGGeneric() {
 
 // generate the message
 bool FGGeneric::gen_message() {
-
     string generic_sentence;
     char tmp[255];
+    length = 0;
 
     double val;
 
     for (unsigned int i = 0; i < _out_message.size(); i++) {
 
-        if (i > 0)
+        if (i > 0 && !binary_mode)
            generic_sentence += var_separator;
 
         switch (_out_message[i].type) {
         case FG_INT:
             val = _out_message[i].offset +
                   _out_message[i].prop->getIntValue() * _out_message[i].factor;
-            snprintf(tmp, 255, _out_message[i].format.c_str(), (int)val);
+            if (binary_mode) {
+                *((int*)&buf[length]) = (int)val;
+                length += sizeof(int);
+            } else {
+                snprintf(tmp, 255, _out_message[i].format.c_str(), (int)val);
+            }
             break;
 
         case FG_BOOL:
-            snprintf(tmp, 255, _out_message[i].format.c_str(),
-                               _out_message[i].prop->getBoolValue());
+            if (binary_mode) {
+                *((bool*)&buf[length]) = _out_message[i].prop->getBoolValue() ? true : false;
+				length += sizeof(bool);
+            } else {
+                snprintf(tmp, 255, _out_message[i].format.c_str(),
+                                   _out_message[i].prop->getBoolValue());
+            }
             break;
 
         case FG_DOUBLE:
             val = _out_message[i].offset +
                 _out_message[i].prop->getFloatValue() * _out_message[i].factor;
-            snprintf(tmp, 255, _out_message[i].format.c_str(), (float)val);
+            if (binary_mode) {
+                *((double*)&buf[length]) = val;
+                length += sizeof(double);
+            } else {
+                snprintf(tmp, 255, _out_message[i].format.c_str(), (float)val);
+            }
             break;
 
         default: // SG_STRING
-             snprintf(tmp, 255, _out_message[i].format.c_str(),
-                                _out_message[i].prop->getStringValue());
+            if (binary_mode) {
+                const char *strdata = _out_message[i].prop->getStringValue();
+				int strlength = strlen(strdata);
+
+				/* Format for strings is 
+				 * [length as int, 4 bytes][ASCII data, length bytes]
+				 */
+                *((int*)&buf[length]) = strlength;
+				length += sizeof(int);
+				strncpy(&buf[length], strdata, strlength);
+				length += strlength; 
+				/* FIXME padding for alignment? Something like: 
+				 * length += (strlength % 4 > 0 ? sizeof(int) - strlength % 4 : 0;
+				 */
+            } else {
+                snprintf(tmp, 255, _out_message[i].format.c_str(),
+                                   _out_message[i].prop->getStringValue());
+            }
         }
 
-        generic_sentence += tmp;
+        if (!binary_mode) {
+            generic_sentence += tmp;
+        }
     }
 
-    /* After each lot of variables has been added, put the line separator
-     * char/string
-     */
-    generic_sentence += line_separator;
- 
-            
-    length =  generic_sentence.length();
-    strncpy( buf, generic_sentence.c_str(), length );
+    if (!binary_mode) {
+        /* After each lot of variables has been added, put the line separator
+         * char/string
+         */
+        generic_sentence += line_separator;
+
+        length =  generic_sentence.length();
+        strncpy( buf, generic_sentence.c_str(), length );
+    } else {
+        // add the footer to the packet ("line")
+        switch (binary_footer_type) {
+            case FOOTER_LENGTH:
+                binary_footer_value = length;
+                break;
+
+            case FOOTER_MAGIC:
+                break;
+        }
+        if (binary_footer_type != FOOTER_NONE) {
+            *((int*)&buf[length]) = binary_footer_value;
+            length += sizeof(int);
+        }
+    }
 
     return true;
 }
@@ -133,6 +181,9 @@ bool FGGeneric::parse_message() {
     double val;
     int i = -1;
 
+	if (binary_mode)
+		printf("generic protocol: binary mode input is not yet implemented.\n");
+	
     while ((++i < (int)_in_message.size()) &&
            p1 && strcmp(p1, line_separator.c_str())) {
 
@@ -230,6 +281,12 @@ bool FGGeneric::close() {
 void
 FGGeneric::read_config(SGPropertyNode *root, vector<_serial_prot> &msg)
 {
+    if (root->hasValue("binary_mode"))
+        binary_mode = root->getBoolValue("binary_mode");
+    else 
+        binary_mode = false;
+
+    if (!binary_mode) {
         /* These variables specified in the $FG_ROOT/data/Protocol/xxx.xml
          * file for each format
          *
@@ -269,7 +326,21 @@ FGGeneric::read_config(SGPropertyNode *r
                 line_separator = '\v';
         else
                 line_separator = line_sep_string;
-
+    } else {
+        binary_footer_type = FOOTER_NONE; // default choice
+        if ( root->hasValue("binary_footer") ) {
+            string footer_type = root->getStringValue("binary_footer");
+            if ( footer_type == "length" )
+                binary_footer_type = FOOTER_LENGTH;
+            else if ( footer_type.substr(0, 5) == "magic" ) {
+                binary_footer_type = FOOTER_MAGIC;
+                binary_footer_value = strtol(footer_type.substr(6, 
+                            footer_type.length() - 6).c_str(), (char**)0, 0);
+            } else if ( footer_type != "none" )
+                printf("generic protocol: Undefined generic binary protocol"
+					   "footer, using no footer.\n");
+        }
+    }
 
     vector<SGPropertyNode_ptr> chunks = root->getChildren("chunk");
     for (unsigned int i = 0; i < chunks.size(); i++) {
Index: src/Network/generic.hxx
===================================================================
RCS file: /var/cvs/FlightGear-0.9/source/src/Network/generic.hxx,v
retrieving revision 1.6
diff -u -p -r1.6 generic.hxx
--- src/Network/generic.hxx	21 Feb 2006 01:19:47 -0000	1.6
+++ src/Network/generic.hxx	25 Apr 2006 01:41:26 -0000
@@ -78,6 +78,10 @@ private:
     vector<_serial_prot> _out_message;
     vector<_serial_prot> _in_message;
 
+    bool binary_mode;
+    enum {FOOTER_NONE, FOOTER_LENGTH, FOOTER_MAGIC} binary_footer_type;
+    int binary_footer_value;
+
     void read_config(SGPropertyNode *root, vector<_serial_prot> &msg);
 
 };
import struct

f = open("bintest_out", "rb")
strf = f.read()

while len(strf) >= 50:
	# Unpack first 6 fields including string length
	tuple = struct.unpack("iddibxxxi", strf[0:32])
	ias, pitch, heading, temp, autocoord, strlen, = tuple
	print "ias = %i pitch = %f heading = %f" % (ias, pitch, heading)
	print "temp = %d autocoord = %i strlen = %i" % (temp, autocoord, strlen)

	# Use string length to extract the string (clock time)
	strdata = strf[32:32+strlen]
	strdata = len(strdata) > 40 and strdata[:40] + "..." or strdata
	print "str_data = %s" % repr(strdata)

	magic = struct.unpack("I", strf[32 + strlen:36 + strlen])
	print "footer (magic number) = 0x%x" % magic
	print "-------------------------------------------------"

	# Discard the data used, and loop
	strf = strf[36 + strlen:]

Reply via email to