Programming FSMs for Dummies

How Do I get a new FSM to start up?
Whats with CoordinateEvent
Thats wonderful! But how does the ProblemSolver create an event?
Argh! Messages and Events! Whats the Difference?
How do I send my FSM a message?
Where is the reply_to field set?
How do I receive a message?
Can I receive many messages of the same type?
Forget Messages. I want to use Events in Transitions
How do I send an event back to the ProblemSolver
ARGH! The damned thing wont parse my transition!
Wah! I dont like how you did X

How Do I get a new FSM to start up?


Well, there are three ways to start a new FSM. You could set up your FSM so that a message from an external source starts it. Or, you could set it up so that an event from inside the agent starts it. Or, finally, you could cheat and start it by hand by hardcoding it into your Problem Solver. This last method is not recommended since hardcoding is bad, and you could just create an event. However, I use the last method.

Initiated by a Message

Every message destined for the Coordination bean has a field that tells the Coordination bean which FSM to deliver it to. For some messages, however, we dont have an FSM to delvier it to, we want the message to start up a new FSM! These messages are given a deliver-to field of "-1". You can cause your FSM to "listen" for these types of messages and respond to them (by creating a new instance of itself). You do this by using the InitiatedBy parameter. An entry in the InitiatedBy list has the format: A -> B, where A is the message label, and B is a function to run when message A arrives. The message label is defined as the first word after the performative. For example, if the message was (tell nle-enables blah blah), the message label would be "nle-enables". You need to add a "msg:" to the beginning of your message label to tell the Coordination bean that you are talking about a message. The Coordination bean will then check with your FSM if any message with the correct label arrives. If one does, a new FSM will be created and added to the active list.

Wait a minute! What if there was some information in that message that you want to process! How do you get at the data of that message? Thats what the function in the InitiatedBy list is for! When the message arrives, the Coordination bean creates a new CoordinateEvent, where the data is the KQMLMessage object. The function parameter can be any function that takes one parameter, a CoordinateEvent object. Just use the coordinateEvent.getFirstData() to retrieve the KQMLMessage object. Here's an example:

InitiatedBy: < msg:nle-enables -> initByEnables>

... (later, in the Functions section) 

public void initByEnables(CoordinationEvent ce)
  {
    KQMLMessage msg = (KQMLMessage)ce.getFirstData();
    ...
  }

So, whenever a message arrives with the format: (performative nle-enables ...) (where performative is any KQML performative), a new FSM will be created, added to the active list, and that FSMs function "initByEnables" will be called.

But what If I dont wanna run any function? Simple. Just do this:

 InitiatedBy:  noFunction>  

(noFunction is actually a function provided by the FSM base class that does nothing)

Initiated By an Event

Initiating an FSM by an event is remarkably similar to initiating one by a message. In fact, the only difference from the FSM designer's point of view, is that the label should not start with "msg:". Here's an example:

 
InitiatedBy: < new-enables-ir -> init>

...

public void init(CoordinateEvent ce)
{
    // CoordinateEvent.  Type = new-enables-ir
    //                   Data = NLE, enablee
 	
    Vector v = (Vector)ce.getFirstData();
...

This did not use to work before. It does now.

Whats up with CoordinateEvent

CoordinateEvent has two fields, type and data. Type is simply the label of the event. It is what gets matched with the event labels you specify in your FSM scripts. Data can be an ugly beast. It is a Vector of Objects. It is used to store any data you want transferred. For example, in the above "new-enables-ir" example, the problem sovler will create the "new-enables-ir" coordination event. It wants to send along three pieces of data about that event to the Coordination bean, the Interrelationship, the agent on the other end of the relationship, and the subjective view. (Yeah, we can get the subjective view from State, but whatever). These three things are all elements of the data vector. There are two CoordinateEvent member functions we can use to get at data. First, theres the simple getData that returns the entire data vector. Then there's getFirstData, which returns the first object in the vector. This is useful if the vector only has one object. Why dont we just make data an object (which can be a vector)? The code is in agent.mass.CoordinateEvent. Go ahead, but look here for info on changing stuff

Thats wonderful! But how does the ProblemSolver create an event?

Pretty easy. Look in agent.mass.CoordinateEvent for all the Constructors. I like the third constructor, which takes a String (the type), an single data Object (which becomes the first element in the data vector), and the standard source object (pointer to the bean that created the object (just use "this")), and an ID (I just use 0). Here's an example from TransporterProblemSolver.java:

   CoordinateEvent ce = new CoordinateEvent("whatsup", new Integer(time), this, 0);
   fsmcoordination.coordinateEventReceived(ce);
  

Hey! You're not using the Event interface properly!

Oh well. Show me the correct way if you want to.

Argh! Messages and Events! Whats the Difference?

Messages:

Events:

How do I send my FSM a message?

Pretty easy. All you need to do is make sure the message is properly addressed. There are two parts to a Coordination message address. The first part tells the Agent that the message is a Coordination message. This is done by setting the "Coordination" KQML field to the "description" of the Coordination bean you are using. The "description" of a Coordination bean is stored in the field "description". Its just a string. For FSMCoordination, the description is "FSMCoordination". So, to send a message to the FSMCoordination bean, you'd just do:

   msg.addField("Coordination", "FSMCoordination");
   

Now, you might want to address your message to a specific FSM. If you want to do so, you need to know that FSM's unique ID. How do you find that out? Well, all messages generated by an FSM should have a "reply-with" field that contains that FSM's ID. You just put that ID into the "deliver-to-fsm" field of your message. But its even easier than that. The FSM.java base class has a FSMSendMessage function that you REALLY SHOULD use in order to send messages from your FSM. FSMSendMessage takes two parameters, both KQMLMessages. The first is the message you want to send, the second is the message you are replying to. (So FSMSendMessage can grab the ID from the "reply-with" field of the message you are replying to. If you are not replying to any message, just put a "null" in there for the reply-to field. Your message will have a "deliver-to-fsm" field of -1. Note: FSMSendMessage will overwrite the "deliver-to-fsm" and "reply-with" fields, so watch out if you want to do something tricky. Here's an example, where m is the message we just received that we want to reply to.

KQMLMessage replym = new KQMLMessage("tell","(Conflict "+localc.dont_interval_start+ " " +localc.dont_interval_end+")", m.getSourceAddr());
FSMsendMessage(replym, m);

Isn't there an easier way? Yep. There's also FSM.sendSimpleMessage, which takes two strings. The first is your message data (you dont need parentheses, they are added automatically). The second is the name of the agent to send the message to. (Use * for a broadcast message). Send SimpleMessage will create a new KQMLmessage with the performative "tell" and the data that you sent as a parameter, then calls FSMSendMessage. There's a local variable in FSM called "reply_to", which you can use to store the KQMLMessage you want to reply to. Set it with a setReply_to(KQMLMessage). sendSimpleMessage calls FSMSendMessage with the parameter "reply_to". Dont worry, "reply_to" can be null, if you're not replying to anything. Here's an example.

.. received a message m ...
setReply_to(m);
sendSimpleMessage("whats up man","friend");

Where is the reply_to field set?

Two places. The first is by you, whenever you call setReply_to. The second is whenever a Transition fires where the precondition was "MessageReceived("blah")". That message that was received is stored in reply_to.

How do I receive a message?

The easiest way is to use the "MessageReceived" transition. If you want to check for a message in your function code, you can get at the received messages list by looking at the Vector recvMessages. This is a list of KMQLMessages that were addressed to this FSM, AND all unknown messages that did NOT start up a FSM. If you use "MessageReceived", the message is automatically deleted from the recvMessages list. If you dont use "MessageReceived", remember to delete the message yourself, or else it will hang around.

Can I receive many messages of the same type?

Sure. In fact, check out the function RetrieveAllMessages(String type) in FSM.java I use it. It returns a Vector of all the KQMLMessages received that have the type specified. Remember, a message's type is just the first Content word. When you receive messages this way, they are NOT deleted. Use deleteMessages(Vector) to delete a list of messages. Or just remove them from recvMessages yourself. This is the code Brett uses to receive many messages of the same type:

S1|[HRP_multirecv("Ok")] -> [ok_agents = ok_agents + messages.size()]|S1

...

public boolean HRP_multirecv(String s)
{
  messages = RetrieveAllMessages(s);  // messages is a local variable
  if (messages.isEmpty())
    return false;
  else
  {
    // Delete the messages, cause we're gonna receive them.
    deleteMessages(messages);
    return true;
  }
}

Forget Messages. I want to use Events in Transitions.

Simple. Use the Special precondition Event. What does Event do? Just look at the code it produces:

public boolean Event(String type)
{
  Enumeration e = recvEvents.elements();
  while (e.hasMoreElements())
  {
    agent.mass.CoordinateEvent ce = (agent.mass.CoordinateEvent)e.nextElement();
    if (ce.getType().equals(type))
      return true;
  }
  return false;
}

So, Event looks through the recvEvents Vector (provided by the Bean) for events with the same label. If it finds one, it sets the currentevent variable to the event (so you can get at the data) and returns true. Here's an example:

	S1|Event("sup") -> [log.log("Hey Whats up", 1);]|S1

Pretty trivial. Thats because I dont use events in my FSMs, I stick to messages and the State bean. Is there a multi-receiving event function? No. You can make one if you want though.

How do I send an event back to the ProblemSolver?

Create it using one of those CoorinateEvent constructors. Then call fireCoordinateEvent(CoordinationEvent) of your coordination bean. You will have to get a pointer to your Coordination bean to call this function. You can get one through State. I dont have an example of this, since I dont use this. How do I communicate back to the problem solver? I use the PropertyEvent stream. Why? Because. Here's an example of what I do.

state.setProperty("NewCommitment", nlc);
    // The problem sovler should notice that the NewCommitment property
    //  has changed, and reschedule. 

ARGH! The damned thing wont parse my transition!

You probably have some code in your pre or postcondition. The parser is quite simple, and may have problems with certain symbols. I'll fix it if it bothers me. You can try to fix it by looking at fsmcc.jj in Research/GPGP2/fsmcc. But, the easy solution is to do this:

 S1|myprecondition() -> mypostcondition()|S2
      
      ...
      
      boolean myprecondition()  
      {
         complicated code
      }
    

      void mypostcondition()
      {
         complicated code
      }

In fact, the above solution makes your transition list look prettier also.

Wah! I dont like how you did X

Well, fix it or work around it.

Have fun. If you change anything that causes my FSMs to break, your blames will skyrocket, so I'd email either Brett or Ping, or both before you commit any major change.


Brett Benyo
Last modified: Tue Apr 6 22:04:50 MET DST