The MapViewer Federate : Part 4

Previous post.

Now I’ll cover the Federates of our Federation: The Tank and Aircraft Federates. Due to the two Federates are very similar , I’ll show just the tank Federate. Everything can be applicable to both of them.

Here you can find the complete source code of the TankClass. and the Main class (the Federate itself).

To interact with the Federate I’ll use the key reader from here.

This is the starter method of the Federate This is not unlike the basics that I showed this series of posts.

// Run the Federate.
public void runFederate() throws Exception	{
  String serial = UUID.randomUUID().toString().substring(0,5).toUpperCase();
  
  createRtiAmbassador();
  connect();
  createFederation("BasicFederation");
  joinFederation("BasicFederation", "TankFederate" + serial);
  
  // Start our objects managers.
  tankClass = new TankClass( rtiamb );
  
  // Publish and subscribe
  publishAndSubscribe();
  
  System.out.println("====== TANK FEDERATE ======");
  System.out.println("Type:");
  System.out.println("");
  System.out.println(" n + ENTER : New tank");
  System.out.println(" q + ENTER : Quit");
  System.out.println("");
  KeyReader kr = new KeyReader( this, "q" );
  kr.readKeyUntilQuit();

  // Get out!
  exitFromFederation();
}

Because of the IKeyReaderObserver I need to implement two methods:

public class Main implements IKeyReaderObserver {
  private RTIambassador rtiamb;
  private FederateAmbassador fedamb; 
   
...

  @Override
  public void notify( String key ) {
    if ( key.equals("n") ) {
      createTank();
      return;
    }
  }
  
  @Override
  public void whenIdle() {
    // Update only the position
    try {
      tankClass.updatePosition();
      rtiamb.evokeMultipleCallbacks(0.1, 0.3);
    } catch ( Exception e ) {
      //
    }
  }	

...

}

And the KeyReader will do the simulation main loop inside whenIdle() method. Any key pressed will be send to notify() method.

Don’t forget we have a FOM and a SOM to load. The FOM (BasicUnit) will be loaded in createFederation() method and the SOM (Tank or Aircraft) in joinFederation() method.

private void createFederation( String federationName ) throws Exception {
  log( "Creating Federation " + federationName );
  try	{
    URL[] modules = new URL[]{
      (new File("foms/HLAstandardMIM.xml")).toURI().toURL(),
      (new File("foms/unit.xml")).toURI().toURL()				
    };
    rtiamb.createFederationExecution( federationName, modules );
    log( "Created Federation." );
  } catch( FederationExecutionAlreadyExists exists ) {
    log( "Didn't create federation, it already existed." );
  } catch( MalformedURLException urle )	{
    log( "Exception loading one of the FOM modules from disk: " + urle.getMessage() );
    urle.printStackTrace();
    return;
  }
}
private void joinFederation( String federationName, String federateName ) throws Exception  {
  URL[] joinModules = new URL[]{
    (new File("foms/tank.xml")).toURI().toURL()
  };
  rtiamb.joinFederationExecution( federateName, "TankFederateType", 
      federationName, joinModules );   
  log( "Joined Federation as " + federateName );
}

Finally , the method for creating the tank. All this tanks will be friends and will have to appear near coordinates -60.06, -27.70 (somewhere in Brazil). A random UUID will be the name and serial number with 5 hexadecimal characters.

private void createTank() {
    Random random = new Random();
    double lon = -60.065429 + ( random.nextInt(5)+1 / 100 );
    double lat = -27.74914 + ( random.nextInt(5)+1 / 100 );
    Position p = new Position( lon,lat);
    String id = UUID.randomUUID().toString().substring(0,5).toUpperCase();
    try {
      tankClass.createNew(id,id, p, TankObject.FRIEND );
    } catch ( Exception e ) {
      log("Error when creating a new Tank: " + e.getMessage() );
    }
  }

As you know, I like to create a kind of “class manager” to hold the RTI class handles ( class and attribute handles ) and a Java class to represents the RTI “objects” that I’ll instantiate.

Basic Federation

Basic Federation

This is the TankClass class.

public class TankClass {
  // The RTI Ambassador - So we can talk to the RTI from here.
  private RTIambassador rtiamb;
  // We must hold the handle of this class 
  private ObjectClassHandle tankHandle;
  // A list of Tank objects we will instantiate
  private List<TankObject> instances;
  // An encoder helper
  private EncoderDecoder encoder;
  // We must hold the handle for all attributes of this class
  // Just Model for now.
  private AttributeHandle modelHandle;
  private AttributeHandle nameHandle;
  private AttributeHandle serialHandle;
  private AttributeHandle imageNameHandle;
  private AttributeHandle positionHandle;
  private AttributeHandle unitTypeHandle;
  
  // Hold all our registered attributes  
  private AttributeHandleSet attributes;

...

}

You can see a place to store all RTI handles and a list to store all our Tanks (TankObject). Now we must ask the RTI to tell us all this class handles.

This is the constructor:

public TankClass( RTIambassador rtiamb ) throws Exception {
  // Get the RTIAmbassador. 
  this.rtiamb = rtiamb;
  // Ask the RTI for the class handle of our Tank.
  this.tankHandle = rtiamb.getObjectClassHandle( "HLAobjectRoot.BasicUnit.Tank" );
  // Get the class handle for the Model attribute of the Tank
  this.modelHandle = rtiamb.getAttributeHandle( tankHandle, "Model" );
  this.nameHandle = rtiamb.getAttributeHandle( tankHandle, "Name" );
  this.serialHandle = rtiamb.getAttributeHandle( tankHandle, "Serial" );
  this.imageNameHandle = rtiamb.getAttributeHandle( tankHandle, "ImageName" );
  this.positionHandle = rtiamb.getAttributeHandle( tankHandle, "Position" );
  this.unitTypeHandle = rtiamb.getAttributeHandle( tankHandle, "UnitType" );

  this.attributes = rtiamb.getAttributeHandleSetFactory().create();
  attributes.add( modelHandle );
  attributes.add( nameHandle );
  attributes.add( serialHandle );
  attributes.add( imageNameHandle );
  attributes.add( unitTypeHandle );
  attributes.add( positionHandle );
  
  
  // Our Tank list ( created by us or discovered )
  instances = new ArrayList<TankObject>();
  // Our encoder helper.
  encoder = new EncoderDecoder();
}

As you can see, I’m taking attributes from BasicUnit (from FOM) and Tank (from SOM) because Tank is a kind of BasicUnit. Now we have all this stuff, we can publish our Tank attributes. Anyone who subscribe to Tank OR BasicUnit will take our updates.

public void publish() throws RTIexception {
  rtiamb.publishObjectClassAttributes( tankHandle, attributes );
}

If you want to see other Tanks ( Tank war! ) you can subscribe too.

public void subscribe() throws RTIexception {
  rtiamb.subscribeObjectClassAttributes( tankHandle, attributes );		
}

If you need to subscribe/publish specific attributes you can take the code from the constructor and create separated methods like publishPosition() or something like that.

To create a new Tank, we must ask the RTI to register a new object of “Tank” class. We must give the Tank class handle:

public ObjectInstanceHandle createNew(String name, String serial, Position position,int unitType) throws RTIexception {
  ObjectInstanceHandle coreObjectHandle = rtiamb.registerObjectInstance( tankHandle );
  TankObject to = new TankObject(coreObjectHandle, unitType);
  to.setName(name);
  to.setSerial(serial);
  to.setPosition(position);		
  log("Tank " + to.getName() + " created.");
  instances.add( to );
  return coreObjectHandle;
}

A new TankObject was created to hold the RTI object instance handle and the status of this specific Tank and then this new Tank was added to the Tanks list.  You may want to override this method to allow you create a new object in the list when a new Tank was discovered if you decide to subscribe to the Tank attributes. I’ll not do this, then this method will never be called. Note the call to the method  rtiamb.requestAttributeValueUpdate(): I’ll explain below.

public ObjectInstanceHandle createNew( ObjectInstanceHandle tankObjectHandle ) throws RTIexception {
    instances.add( new TankObject(coreObjectHandle, TankObject.UNKNOWN ) );
    rtiamb.requestAttributeValueUpdate(tankObjectHandle, attributes);
    return tankObjectHandle;
}

Sometimes a Federate may want to get an “up to date” state of an object of other Federate. Is a good practice to create a method to answer this to the provideAttributeValueUpdate() RTI Ambassador event.

public void provideAttributeValueUpdate(ObjectInstanceHandle theObject,	
    AttributeHandleSet theAttributes ) {
  log("Update attribute request for object " + theObject);
  for ( TankObject tank : instances ) {
    if( tank.isMe( theObject ) ) {
      try {
        updateAttributeValuesObject( tank );
      } catch ( Exception e ) {
        log("Error when send attributes updates");
      }
      return;
    }
  }
}

Now, when a new tank get in the Federation, other Federates can call rtiamb.requestAttributeValueUpdate(newTankObjHandle, whatAttributesIWant). It will enable the new Tank send their attributes as soon as they detect its presence. It is a good idea if you don’t want to care about the execution order of  the Federates. We also don’t need to send attributes to the RTI, simply wait for someone to ask us.

Here is where we’ll send the attributes. Remember: this will be called only under demand. We will push only the Tank position in main simulation loop.

public void updateAttributeValuesObject( TankObject tank ) throws Exception {
  // Convert Java types to the OMT types
  HLAunicodeString modelValue = encoder.createHLAunicodeString( tank.getModel() );
  HLAunicodeString nameValue = encoder.createHLAunicodeString( tank.getName() );
  HLAunicodeString serialValue = encoder.createHLAunicodeString( tank.getSerial() );
  HLAunicodeString imageNameValue = encoder.createHLAunicodeString( tank.getImageName() );
  HLAinteger32BE unitTypeValue = encoder.createHLAinteger32BE( tank.getUnitType() );
  
  // Create a package to send to the RTI
  AttributeHandleValueMap attributes = rtiamb.getAttributeHandleValueMapFactory().create(4);
  // We must tell the attribute handle of this attribute value so the RTI can identify it.
  attributes.put( modelHandle, modelValue.toByteArray() );
  attributes.put( nameHandle, nameValue.toByteArray() );
  attributes.put( serialHandle, serialValue.toByteArray() );
  attributes.put( imageNameHandle, imageNameValue.toByteArray() );
  attributes.put( unitTypeHandle, unitTypeValue.toByteArray() );			
  attributes.put( positionHandle, encoder.encodePosition( tank.getPosition() ) );			
  
  // When send the attributes to update, we must tell the RTI what specific object is it owner.
  // we must give the object handle of this Tank. We can send a tag to track this operation
  rtiamb.updateAttributeValues( tank.getHandle(), attributes, "Tank Update".getBytes() );
}

Nothing new (I hope). Here we’ll encode all Tank attribute values and send to the people.

At last, one method to send only the Position attribute.

public void updatePosition() throws Exception {
  for ( TankObject tank : instances  ) {
    tank.update();
    AttributeHandleValueMap attributes = rtiamb.getAttributeHandleValueMapFactory().create(1);
    attributes.put( positionHandle, encoder.encodePosition( tank.getPosition() ) );			
    rtiamb.updateAttributeValues( tank.getHandle(), attributes, "Tank Position".getBytes() );
  }
}

Now we can send all tanks positions without send static attributes too. You will see Main.whenIdle() calling this method.

Next post: the TankObject.

Add Comment