Tutorial: Creating an Experimental JAUS Message PDF Print E-mail
Written by Danny Kent   

Download the code for this example:

ExperimentalMessage.zip


Introduction
Part 1: Defining a JAUS Compatible Message
Part 2: Implementing an Experimental Message with OpenJAUS

Introduction

The JAUS standard provides over 200 messages for a large variety of data and situations. However, sometimes a project or developer just needs something else. Perhaps the project has a novel sensor algorithm or some new exciting way to do vehicle control. Often in that situation the developer will look at JAUS and say "it limits my ability to solve the problem creatively." That is simply not true though! Rather, JAUS not only provides a standard set of messages to marshal the majority of your unmanned system's data, but also a framework within which you can easily (and quickly) build new functionality.

For example, lets say we have a new vehicle planning algorithm we want to test. The planning algorithm works by outputting commands as a desired platform velocity state. We look through our list of JAUS components and messages and see that there is no driver mechanism for commanding a velocity state. This is a problem which would lead us to being unable to use the JAUS standard. However, we can easily build a message which has all the knowledge we need to communicate in it and use it in our system. We can even use it with other JAUS systems, as long as they too have the definition of the JAUS message and can implement the message on their system. If both JAUS systems are built on OpenJAUS, they only need our source and header files and can compile support into their system easily! We'll see how to create our source code a bit later in the tutorial. For now, we will concentrate on defining our custom (experimental) JAUS message. (NOTE: The need for this particular functionality, velocity state command, is widely known in the JAUS community and is being incorporated into the next version of the specification. However it makes a good example for our purposes here.)

Part 1: Defining a JAUS Compatible Message

To define our JAUS message, we'll want to consider the data within the message we will need. The JAUS standard defines a set of data types in the Reference Architecture, Version 3.3, Part 2 in Table 2-1 on page 2. This table is reproduced below for convenience. This defines all the data members we can use as we define our custom JAUS message. OpenJAUS provides code to handle the packing and unpacking of each of these data types to and from byte buffers. When designing a message, it is also important to understand the concept of Scaled Integers. Many JAUS messages use scaled integer values and they are discussed in more detail in Part 2 of the JAUS RA in section 2.2.1. Essentially, given a upper and lower boundary, a floating point number can be represented as an integer and converted to and from that integer. This is often used in JAUS to conserve bandwidth in the message set.

Data Type Size (in bytes) Representation
 Byte 1  8 bit unsigned integer
 Short Integer 2  16 bit signed integer
 Integer 4  32 bit signed integer
 Long Integer 8  64 bit signed integer
 Unsigned Short Integer 2  16 bit unsigned integer
 Unsigned Integer 4  32 bit unsigned integer
 Unsigned Long Integer 8  64 bit unsigned integer
 Float 4  IEEE 32 bit floating point number
 Long Float 8  IEEE 64 bit floating point number

For our purposes, we want to design a message to transport a desired velocity state command. To keep this example simple, we will limit our commanded velocity state to a common Omega/Velocity control setup, where we are commanding the linear velocity of the platform and the angular velocity of the platform (this is often used in skid-steer or differential drive ground vehicles). This means that we will need to send only two pieces of information, the velocity and the omega. Both of these data members are going to be real values, so we might chose to represent them as Floats (from the table above). However, the observant JAUS developer will notice that VERY few (almost none) of the JAUS messages actually use floats. They almost all use scaled integers. While the choice of which to use in an experimental message is ultimately up to the developer and system needs, it is important to note here that most times a Scaled Integer is preferred over a Float or a Long Float. To make our example more complex (and probably more useful), we have chosen to implement our Platform Velocity and Omega Command message with scaled integers. The question then is, what is the maximum and minimum values for our scaled integers? First we will need to define the units that our values will be expressed in. As with all scientific values, a number is useless without the corresponding units! Well it happens that in this case, JAUS provides us with a guide for that. While a developer can easily pick units and corresponding upper and lower boundary values which make sense to their particular application, in this case there is a similar JAUS message that already defines these for us. JAUS does provide a method for reporting the velocity state of a given platform, the message is Code 4404h: Report Velocity State. In this message, the velocity of the platform in all six degrees of freedom is defined. We are only interested in the linear along the major vehicle axis and the rotation about the vertical axis these are "Velocity X" and "Yaw Rate" respectively in the Report Velocity State message. From that message we can see that the velocity is defined as a Scaled Integer reported in "Meters per Second" which has an upper bound of 65.534 and lower bound of -65.534. Also, yaw rate is defined as a Scaled Short Integer reported in "Radians per Second" which has an upper bound of 32.767 and a lower bound of -32.767. (NOTE: The astute developer will notice a similarity between these boundary values and the maximum value of a signed integer and signed short respectively. This is not required and was probably chosen by the JAUS developer for simplicity. Any valid real value can be used for the upper and lower bound.) So we now have defined our two fields. While we could certainly call them Field 1 and Field 2, it is often better to give each some unique name which is easier for human consumption, let's call them Velocity and Omega.

The last thing required to define a JAUS compatible message is to select a command code. The command code is very important as it identifies your message and tells other JAUS entities what to expect in the data portion of your message. JAUS defines a range of command codes which have been set aside for experimental messages. This range is defined as D000h – FFFFh in Table 3-1 of the JAUS Reference Architecture, Part 2, Version 3.3. We can choose any value in that range we like for our message. One important thing to note here is that your command code is a free choice, as it is for all JAUS developers, so the chance remains you could always have a conflict with experimental message command codes in the future if you try to integrate with other JAUS systems which may have experimental messages as well. For our demonstrative purposes any command code will suffice, so D000h will be used.

 

Recap

To recap, we have defined a custom JAUS message with a command code of D000h which contains two bits of information a Velocity field which is a scaled integer and a Omega field which is a scaled short integer. JAUS normally defines messages in a table format (examples of this can be seen throughout the JAUS Reference Architecture, Part 3, Version 3.3). We define our message in the same table format below:

 

Code D000h: Platform Velocity and Omega Command

Field # Name Type Units Interpretation (Note)
1  Velocity  Scaled Integer  Meters per Second  Scaled Integer
    Lower Limit = -65.534
    Upper Limit = 65.534
2 Omega Scaled Short Integer Radians per Second  Scaled Integer
    Lower Limit = -32.767
    Upper Limit = 32.767

We will now look at how a developer can implement this message using OpenJAUS. It is important to note before moving on that there are many other mechanisms in use throughout the JAUS message set to format and organize the data in messages. Things such as bit fields, enumerations and presence vectors are important concepts which may have bearing on the design and implementation of a custom JAUS message. It is left to the developer to find examples of these in the JAUS message set (there are many) and then explore how these different constructs are used and implemented in the OpenJAUS code base.

Part 2: Implementing an Experimental Message in OpenJAUS

Now that our message is well defined, we need to implement it in some source code. In OpenJAUS there are literally hundreds of messages defined in the libjaus package. While looking at one of these might make a good example, OpenJAUS instead provides a more useful mechanism. OpenJAUS provides a "template" message in the libjaus/src/message folder called skeletonMessage.c.tmpl and skeletonMessage.h. We will use these templates to create our Platform Velocity and Omega Command message. First, lets take a look at skeletonMessage.h.


// File Name: xXXXMessage.h
// Written By: 
// Version: 3.3.0
// Date: 07/09/08
// Description: This file defines the attributes of a XxXxMessage

#ifndef XXXX_MESSAGE_H
#define XXXX_MESSAGE_H 
#include "jaus.h"

typedef struct
{
   // Include all parameters from a JausMessage structure:
   // Header Properties
   struct
   {
      // Properties by bit fields
      #ifdef JAUS_BIG_ENDIAN
         JausUnsignedShort reserved:2;
         JausUnsignedShort version:6;
         JausUnsignedShort expFlag:1;
         JausUnsignedShort scFlag:1;
         JausUnsignedShort ackNak:2;
         JausUnsignedShort priority:4; 
      #elif JAUS_LITTLE_ENDIAN
         JausUnsignedShort priority:4; 
         JausUnsignedShort ackNak:2;
         JausUnsignedShort scFlag:1; 
         JausUnsignedShort expFlag:1;
         JausUnsignedShort version:6; 
         JausUnsignedShort reserved:2;
      #else
         #error "Please define system endianess (see jaus.h)"
      #endif
   }properties;

   JausUnsignedShort commandCode;
   JausAddress destination;
   JausAddress source;
   JausUnsignedInteger dataSize;
   JausUnsignedInteger dataFlag;
   JausUnsignedShort sequenceNumber;

   // MESSAGE DATA MEMBERS GO HERE     

   // Example from ReportGlobalPoseMessage
   //
   // JausUnsignedShort presenceVector;
   // JausDouble latitudeDegrees;    // Scaled Int (-90, 90)
   // JausDouble longitudeDegrees;   // Scaled Int (-180, 180)
   // JausDouble elevationMeters;    // Scaled Int (-10000, 35000)
   // JausDouble positionRmsMeters;  // Scaled UInt (0, 100)
   // JausDouble rollRadians;        // Scaled Short (-JAUS_PI, JAUS_PI)
   // JausDouble pitchRadians;       // Scaled Short (-JAUS_PI, JAUS_PI)
   // JausDouble yawRadians;         // Scaled Short (-JAUS_PI, JAUS_PI)
   // JausDouble attitudeRmsRadians; // Scaled Short (0, JAUS_PI)
   // JausUnsignedInteger timestamp;

}XxXxMessageStruct;

typedef XxXxMessageStruct* XxXxMessage; 

JAUS_EXPORT
XxXxMessage xXXXMessageCreate(void);

JAUS_EXPORT
void xXXXMessageDestroy(XxXxMessage);

JAUS_EXPORT
JausBoolean xXXXMessageFromBuffer(XxXxMessage message, unsigned char* buffer, unsigned int bufferSizeBytes);

JAUS_EXPORT
JausBoolean xXXXMessageToBuffer(XxXxMessage message, unsigned char *buffer, unsigned int bufferSizeBytes);

JAUS_EXPORT
XxXxMessage xXXXMessageFromJausMessage(JausMessage jausMessage);

JAUS_EXPORT
JausMessage xXXXMessageToJausMessage(XxXxMessage message);

JAUS_EXPORT
unsigned int xXXXMessageSize(XxXxMessage message); 

#endif // XXXX_MESSAGE_H

This and the skeletonMessage.c file both use a CaSe SeNsItIvE encoding scheme which allows the developer to easily refactor the skeletonMessage. Three such case sensitive keys are used:

  1. "XXXX" - this is used as a stand in for defines and should be the name of the message with underscores for spaces between words, for example: PLATFORM_VELOCITY_OMEGA_COMMAND
  2. "XxXx" - this is a the name of the message, starting with a capital letter, for example: PlatformVelocityOmegaCommand
  3. "xXXX" - this is the name of the message, starting with a lower case, for example: platformVelocityOmegaCommand

In all three cases, the word "Message" is omitted as it is included in the template already. A simple find and replace on these (remember case sensitive and not whole word replace) is all that is required. Also remember to rename your header and source file. The standard practice is to use a lower case naming convention for file names, such as "platformVelocityOmegaCommandMessage.h" Once the name has been properly setup, we need to define our data members. Data members are added after the line "MESSAGE DATA MEMBERS GO HERE." In the skeleton files, a short example from the ReportGlobalPoseMessage is included. ReportGlobalPoseMessage is a good example of a complex message which uses a presence vector and a lot of scaled integer values. For our message, we will remove these and replace them with our own data members.

Now we have platformVelocityOmegaCommand.h which looks like the following:


// File Name: platformVelocityOmegaCommandMessage.h
// Written By: OpenJAUS.com
// Version: 3.3.0
// Date: 07/09/08
// Description: This file defines the attributes of a PlatformVelocityOmegaCommandMessage

#ifndef PLATFORM_VELOCITY_OMEGA_COMMAND_MESSAGE_H
#define PLATFORM_VELOCITY_OMEGA_COMMAND_MESSAGE_H

#include "jaus.h"

#define JAUS_PLATFORM_VELOCITY_OMEGA_COMMAND 0xD000

typedef struct
{
   // Include all parameters from a JausMessage structure:
   // Header Properties
   struct
   {
   // Properties by bit fields
   #ifdef JAUS_BIG_ENDIAN
      JausUnsignedShort reserved:2;
      JausUnsignedShort version:6;
      JausUnsignedShort expFlag:1;
      JausUnsignedShort scFlag:1;
      JausUnsignedShort ackNak:2;
      JausUnsignedShort priority:4;
   #elif JAUS_LITTLE_ENDIAN
      JausUnsignedShort priority:4;
      JausUnsignedShort ackNak:2;
      JausUnsignedShort scFlag:1;
      JausUnsignedShort expFlag:1;
      JausUnsignedShort version:6;
      JausUnsignedShort reserved:2;
   #else
      #error "Please define system endianess (see jaus.h)"
   #endif
   }properties;
   JausUnsignedShort commandCode;
   JausAddress destination;
   JausAddress source;
   JausUnsignedInteger dataSize;
   JausUnsignedInteger dataFlag;
   JausUnsignedShort sequenceNumber;

   // MESSAGE DATA MEMBERS GO HERE
   JausDouble velocityMetersPerSecond;    // Scaled Int (-64.534, 65.534)
   JausDouble omegaRadiansPerSecond;      // Scaled Short (-32.767, 32.767)

}PlatformVelocityOmegaCommandMessageStruct;

typedef PlatformVelocityOmegaCommandMessageStruct* PlatformVelocityOmegaCommandMessage;

JAUS_EXPORT
PlatformVelocityOmegaCommandMessage platformVelocityOmegaCommandMessageCreate(void);

JAUS_EXPORT
void platformVelocityOmegaCommandMessageDestroy(PlatformVelocityOmegaCommandMessage);

JAUS_EXPORT
JausBoolean platformVelocityOmegaCommandMessageFromBuffer(PlatformVelocityOmegaCommandMessage message, unsigned char* buffer, unsigned int bufferSizeBytes);

JAUS_EXPORT
JausBoolean platformVelocityOmegaCommandMessageToBuffer(PlatformVelocityOmegaCommandMessage message, unsigned char *buffer, unsigned int bufferSizeBytes);

JAUS_EXPORT
PlatformVelocityOmegaCommandMessage platformVelocityOmegaCommandMessageFromJausMessage(JausMessage jausMessage);

JAUS_EXPORT
JausMessage platformVelocityOmegaCommandMessageToJausMessage(PlatformVelocityOmegaCommandMessage message);

JAUS_EXPORT
unsigned int platformVelocityOmegaCommandMessageSize(PlatformVelocityOmegaCommandMessage message);

#endif // PLATFORM_VELOCITY_OMEGA_COMMAND_MESSAGE_H


At this point, one may be wondering to themselves, "I thought our velocity and omega were scaled integers, but here they are defined as a JausDouble, why is that?" This is a good observation. In OpenJAUS, all Scaled Integer values are stored in the message data structures as floating point values (JausDouble). This is for ease of the end user. The fact that a value is a scaled integer, which is perhaps of interest to the end user, should not make using the data cumbersome to said user. Rather, the user of the data is free to use the message members (in this case velocity and omega) as they would any other floating point value in their code. Only during the process of packing and unpacking a message to a byte buffer is the fact that a value is a signed integer of any significance. We will show how this is accomplished in the source file below. Lastly, there is one more thing we will add to the file. As we will see in the source file, it is necessary to define the command code of our experimental message. This is not part of the standard skeletonMessage template, as typically command codes are defined elsewhere for non-experimental messages. However, it is easier to add them to the header file for our purposes. This is done with the line shown below.

#define JAUS_PLATFORM_VELOCITY_OMEGA_COMMAND 0xD000

Our header file is now complete, as we have converted the skeletonMessage.h file into platformVelocityOmegaCommandMessage.h and added our two data members. We will now look at the accompanying source file and how to implement our message behavior.

Creating the source file from skeletonMessage.c is a similar exercise in find and replace as is done in the header file. Once this is done, the developer need only concentrate on implementing the methods defined in the top part of the source file. Shown below is the part of the file of interest.


static const int commandCode = JAUS_PLATFORM_VELOCITY_OMEGA_COMMAND;

// *******************************************************
// USER CONFIGURED FUNCTIONS
// *******************************************************

// Initializes the message-specific fields
static void dataInitialize(PlatformVelocityOmegaCommandMessage message)
{
   // Set initial values of message fields
}

// Destructs the message-specific fields
static void dataDestroy(PlatformVelocityOmegaCommandMessage message)
{
   // Free message fields
}

// Return boolean of success
static JausBoolean dataFromBuffer(PlatformVelocityOmegaCommandMessage message, unsigned char *buffer, unsigned int bufferSizeBytes)
{
   int index = 0;

   if(bufferSizeBytes == message->dataSize)
   {
      // Unpack Message Fields from Buffer
      return JAUS_TRUE;
   }
   else
   {
      return JAUS_FALSE;
   }
}

// Returns number of bytes put into the buffer
static int dataToBuffer(PlatformVelocityOmegaCommandMessage message, unsigned char *buffer, unsigned int bufferSizeBytes)
{
   int index = 0;

   if(bufferSizeBytes >= dataSize(message))
   {
   // Pack Message Fields to Buffer
   }
   return index;
}

// Returns number of bytes put into the buffer
static unsigned int dataSize(PlatformVelocityOmegaCommandMessage message)
{
   int index = 0;
   return index;
}

The first item of interest is the commandCode member. This uses the value defined in the header, which was shown above in the discussion of the header file. Following this is five functions which must be populated by the developer. These five functions define the behavior of the message and how it is packed and unpacked from a byte buffer. The exact implementation of each method for our example is shown below and discussed.


void dataInitialize(...)

This function is used to initialize the data members of the message. In our case both our members are simple JausDouble types which are initialized using the newJausDouble() function. Also, note that the properties.expFlag member is initialized to JAUS_EXPERIMENTAL_MESSAGE. In the JAUS header there is a field (expFlag) which is used to signify when an experimental message is created. It is important to include this line of code in your experimental messages so other systems know that it is experimental, and not just some undefined message command code.


// Initializes the message-specific fields
static void dataInitialize(PlatformVelocityOmegaCommandMessage message)
{
   // Experimental message bit
   message->properties.expFlag = JAUS_EXPERIMENTAL_MESSAGE;

   // Set initial values of message fields
   message->velocityMetersPerSecond = newJausDouble(0.0); // Scaled Int (-65.534, 65.534)
   message->omegaRadiansPerSecond = newJausDouble(0.0);   // Scaled Short (-32.767, 32.767)
}

 


void dataDestroy(...)

Sometimes it is necessary to have some dynamic memory allocated as part of a message structure. In that case, that memory should be checked and de-allocated in this function to avoid memory leaks. In this case, there is nothing to be checked.


// Destructs the message-specific fields
static void dataDestroy(PlatformVelocityOmegaCommandMessage message)
{
   // Free message fields
   // Nothing to free
}

 


JausBoolean dataFromBuffer(...)

This function defines how our message's data members are populated from a byte buffer. Here we define the behavior of extracting each member of our message from the buffer. Since we are using scaled integer values, the value in the buffer is the scaled integer value and not the real floating point value. Therefore we first extract the data to a temporary variable. We then transform the integer value to a floating point value using the appropriate function (jausIntegerToDouble() for example) and provide it with the upper and lower bounds. This is how the scaling of real numbers from integers is abstracted away from the end-user, allowing them to work without knowledge of the way the data is packaged and transported.


// Return boolean of success
static JausBoolean dataFromBuffer(PlatformVelocityOmegaCommandMessage message, unsigned char *buffer, unsigned int bufferSizeBytes)
{
   int index = 0;
   JausInteger tempInt = 0;
   JausShort tempShort = 0;

   if(bufferSizeBytes == message->dataSize)
   {
      // Unpack Message Fields from Buffer

      // Velocity
      if(!jausIntegerFromBuffer(&tempInt, buffer+index, bufferSizeBytes-index)) return JAUS_FALSE;
      index += JAUS_INTEGER_SIZE_BYTES;

      // Scaled Integer (-65.534, 65.534)
      message->velocityMetersPerSecond = jausIntegerToDouble(tempInt, -65.534, 65.534);

      // Omega
      if(!jausShortFromBuffer(&tempShort, buffer+index, bufferSizeBytes-index)) return JAUS_FALSE;
      index += JAUS_SHORT_SIZE_BYTES;

      // Scaled Short (-32.767, 32.767)
      message->omegaRadiansPerSecond = jausShortToDouble(tempShort, -32.767, 32.767);

      return JAUS_TRUE;
    }
    else
    {
        return JAUS_FALSE;
    }
}



int dataToBuffer(...)

This function defines how a message's data members are packed into a byte buffer. Again, because of the use of scaled integers, a temporary variable is used following the double to integer transformation.


// Returns number of bytes put into the buffer
static int dataToBuffer(PlatformVelocityOmegaCommandMessage message, unsigned char *buffer, unsigned int bufferSizeBytes)
{
   int index = 0;
   JausInteger tempInt = 0;
   JausShort tempShort = 0;

   if(bufferSizeBytes >= dataSize(message))
   {
      // Pack Message Fields to Buffer

      // Velocity
      // Scaled Integer (-65.534, 65.534)
      tempInt = jausIntegerFromDouble(message->velocityMetersPerSecond, -65.534, 65.534);

      // pack
      if(!jausIntegerToBuffer(tempInt, buffer+index, bufferSizeBytes-index)) return JAUS_FALSE;
      index += JAUS_INTEGER_SIZE_BYTES;

      // Omega
      // Scaled Short (-32.767, 32.767)
      tempShort = jausShortFromDouble(message->omegaRadiansPerSecond, -32.767, 32.767);

      if(!jausShortToBuffer(tempShort, buffer+index, bufferSizeBytes-index)) return JAUS_FALSE;
      index += JAUS_SHORT_SIZE_BYTES;
   }
   return index;
}

 


unsigned int dataSize(...)

This function calculates the number of bytes used by a message's data members. In our example it is quite simple, but in many other JAUS messages various constructs, like loops and presence vectors, cause the size of a message to be quite dynamic. Therefore it is calculated for each message during runtime.


// Returns number of bytes put into the buffer


static unsigned int dataSize(PlatformVelocityOmegaCommandMessage message)


{


   int index = 0;

   // Velocity
   index += JAUS_INTEGER_SIZE_BYTES;

   // Omega
   index += JAUS_SHORT_SIZE_BYTES;

   return index;
}  


A word about Presence Vectors...
Throughout this tutorial we have talked several times about something called a presence vector. As defined in the JAUS Reference Architecture JAUS messages may have a mixture of required and optional data fields. A presence vector is used when optional data fields are defined for a message. It provides a bit mapping of optional data fields. At runtime, this bit field is parsed and used to determine which fields are packed or unpacked from a message. Therefore a presence vector, if required, is always the first member of a message. A good example of using a presence vector with OpenJAUS can be seen in the ReportGlobalPoseMessage source file.

 

Recap

In this section of the tutorial we have seen how to take the skeletonMessage.c/.h files and refactor them into our custom experimental JAUS message. We have introduced the skeletonMessage files and shown how to replace the keywords with the proper specific message keywords. We have also shown how to define our message's data members in the header file as well as the command code. We have discussed scaled integer values and how OpenJAUS handles them and shown how they are abstracted away from the end-user of our message structure. In the source file, we have covered the five important functions which must be implemented to define our message's behavior and shown a sample implementation for each using our example Platform Velocity and Omega Command message.

 

 

0 Comments

Add Comment

Copyright © 2010 OpenJAUS. All Rights Reserved.
Joomla! is Free Software released under the GNU/GPL License.