OpenJAUS Tutorials
JAUS Components
JAUS Messaging
| Tutorial: Creating an Experimental JAUS Message |
|
|
|
| Written by Danny Kent | |||||||||||||||||||||||||||||||||||||||||||||||
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.
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.
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
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. 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:
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: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.
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. 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. 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. 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. 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.
A word about Presence Vectors...
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