The Finite State Machine Cross Compiler (needs a better name) is used for creating finite state machines that control agent activity. These machines are created by the fsmcc compiler that reads in a FSM script and outputs Java code that implements the script. This Java code needs to be linked into an agent which contains the FSMCoordination bean.
The script file used for creating a FSM is based on the AgenTalk framework. Our version of an AgenTalk script consists of a set of states, a set of transitions, a start state, some final states, a script name, script variables, and possibly the name of a parent script that the script will inherit from. All of these items can be listed in the script file in any order. In addition, we have added some elements to the script that provide extra functionaliy. These are shared variables, event handlers, and initiation rules. These also may be listed in any order. Finally, specific agent functions may be added to the end of a script (only at the end, everything after the functions is ignored).
Script: script_name
The script name will be used as the name of the Java code file that will be created, and thus also the name of the Java class that represents this FSM.
InheritsFrom: parent_name
This FSM class will inherit all of the variables, transitions, event handlers, and functions of the parent script. Currently, this feature has been implemented, but has not been tested at all. Use it at your own risk for the time being...
4/1/99: I'm pretty sure inheritance WONT work as described above. Currently, all the InheritsFrom does is make your FSM class extend the inherited class. This gives you access to all the variables and functions of the parent class, but it does NOT give you the transitions or event handlers.
States: <state state ... state>
The states should be a list of strings separated by whitespace. The entire list of states needs to be surrounded by the greater than/less than symbols. The state names need to be unique.
StartState: state
This is the state that the FSM will begin in. It must be one of the states defined in the State list.
FinalStates: <state ... state>
This is a list of final states of the FSM. Whenever the FSM enters one of these states, it will terminate. This set can be empty, if you want your FSM to continue executing indefinitely.
Variables: <type name = value ... type name >
The variable list is a list of variable definitions. The type should be a variable or Java class. The initial value is optional. The list of variable type/names are separated by whitespace.
Imports: <Java package ... Java Package>
The Imports list is a list of Java packages that need to be included. You should list non-standard packages here that are used by your agent functions. The following packages are automatically imported, and dont need to be listed here: agent.coordinate, java.lang.*, java.util.*, java.io.*, agent.mass.State, agent.simplest.KQMLMessage, agent.simplest.Log, agent.simplest.*.
The heart of a FSM is its transitions table. In our FSMs, a transition consists of four elements, a initial state, a precondition, a final state, and a postcondition. The initial state and final state need to be one of the states defined in the state list. Preconditions and postconditions can be one of three things: calls to functions, an expression that evaluates to a boolean, or a special command. Multiple pre conditions and post condition are allowed. By default, all multiple preconditions are ANDed together. To make the preconditions ORd together instead, you can use the "OR" keyword.
initial state|precondition -> postcondition|final state
The format for preconditions/postconditions is given below. Preconditions must all evaluate to booleans, so any calls to functions must return a boolean. Postconditions have no such restriction.
[(AND | OR) condition; condition;]
The AND or OR keywords are not required, the preconditions are ANDed by default. Also, the AND/OR keywords have no meaning for postconditions. If there is only one precondition that does not contain whitespace (its a function call or special condition, not a expression containing whitespace), the square brackets are not needed (though they dont hurt). Note that the conditions are separated by semicolons. If there is only one condition, do NOT put a semicolon after it. For numerous examples of rules, see the examples section
Its really not as complicated as it may sound.
Currently, the FSM class provides four special pre/post conditions that may be used in rules. Eventually, more may become available as the tool matures. Right now, you can use the following four things as pre or postconditions:
The sendSimpleMessage function will create a new KQML message with the performative "tell" and the content equal to the string you provide. This message will be sent to the agent listed (toagent, which is a string). The MessageReceived function will evaluate to true if a KQML message arrives with the first content word equal to the string provided. Event will evaluate to true if an event occurred that time step with the correct label. The startTimer function will begin a software timer that increments every simulator timestep. The parameter defines the number of clicks after which the timer will go off. The timeout function will evaluate to true if the timer has gone off this timestep.
Shared variables that are accessible to any FSM can be defined through the use of the SharedVariable field. The format for this field is identical to the Variable field.
SharedVariables: <type name = value ... type name >
The variables defined here can only be accessed by other FSMs, not by any other agent component. To define a variable that is accessible by any component of the agent, or to access such a variable defined elsewhere, use the State bean.
Warning: Right now, inheritance does not work properly with shared variables. This means that if your parent class defines some shared variables, they will NOT be accessible by child classes. I will fix this eventually.
Events can be created in one of two ways. Any message that arrives to the FSMCoordination component that is not addressed to a specific FSM is make into an event. These message events are given the label "msg:word", where word is the first content word of the KQML message (regardless of performative). In addition, any agent component can create an event my creating a CoordinationEvent object and registering it with the Coordination bean. These events can have any arbitrary label.
The event list is a list of event labels and event handler functions to be called whenever the FSM receives an event with the appropriate label. The format is:
Events: <event label -> event handler function ... >
FSMs can be initiated manually by the problem solver instantiating a new object of the appropriate class, or by the coordination bean when it receives the appropriate event. If an FSM wants to be initiated by an event, it must register that event in the InitiatedBy list. This list has the same format as the event list. In this case, the event handler is a function that returns void and has one parameter which is a CoordinationEvent object. This function should set any local variables that need to be set.
InitiatedBy: <event label -> initiation function ... >
The last portion of a script file should contain any agent functions that will be needed by the FSM. These functions are copied word for word into the Java class that gets created, so they need to be pure Java code. They can access any of the local or shared variables defined in the Variables or SharedVariables lists. To signify that the remainder of the input file consists of agent functions, use the following keyword:
Functions:
All scripts inherit stuff from the FSM class. This class provides the following local variables that any FSM can access:
The examples in this section implement mechanism 4 of GPGP. The first script is for the initiating agent which discovers the enables relationship. The second script is for the enabled agent. The second script contains shared variables and events that are never used, simply to demonstrate how shared variables and events would be defined.
The Initiator script is invoked directly by a problem solver when it notices an enables relationship. The problem solver would use code like:
GPGP_Mechanism4Initiator newfsm = new GPGP_Mechanism4Initiator(log, state); newfsm.init(ir, ((NonLocalMethod)tonode).agent, struct); fsmcoordination.addFSM(newfsm);
The responder is initiated by the receipt of an unknown message with the content "nle-enables". Both scripts are working with the latest release of all software as of 4/1/99.
GPGP Mechanism 4 InitiatorThis section will provide implementation details about the workings of the FSMCoordination bean. You dont need to know any of this to write scripts, but if you really want to know whats happening (or what is supposed to happen) read on.
Every time interval the Coordination bean first compiles a list of all messages received. It only looks at KQML messages with the field "deliver-to-fsm" set. This field indicates first that the message is for the coordination component, and second, it gives the index of the FSM to deliver the message to. All FSMs have a local index which is a number greater than or equal to zero. If "deliver-to-fsm" is set to -1, this means that the message is for an unknown FSM (maybe one that hasn't been created yet). In this case, a coordination event is created with the label "msg:word", where word is the first content word of the KQML message.
The Coordination bean stores all messages in a hashtable. The keys of this hash table are the FSM ids of all active FSMs, along with the key -1, where unknown messages are stored. The bean then cycles through all of the FSM classes it knows about and executes a method called "initiate" on all of the unknown messages. This method will return a new FSM object if that FSM is initiated by that message.
Next, messages are delivered to each FSM by calling the "deliverMessages" function. This function copies the vector of KQML messages destined for a FSM into a local FSM variable called recvMessages. Then, all coordination events are passed on to each active FSM through the "deliverEvents" function. This function is created by the parser, and it simply calls the event handler functions if any of the registered events are present.
Next each active FSM is pulsed. The FSM method "pulse" does many things. First, it retrieves the current values of all shared variables. Then it updates the timer, if the timer is active. Next, the "enabledTransitions" function is executed. This function is created by the parser and it cycles through all the transitions available from the current state and tests the preconditions. It returns a vector of transitions that have their preconditions evaluate to true. The transitions are checked in the order that the rules are presented in the script, so if multiple rules are enabled, the first rule listed in the script input file will be the one that gets activated.
The "enabledTransitions" function returns a list of all enabled transitions, but only the first element in the list is looked at. The first transition returned is then fired, with a call to "fireTransition", which is another function created by the parser. This function executed the postconditions and updates the current state of the FSM. Finally, the current values of all shared variables are uploaded to the Coordination bean.
After all FSMs are pulsed, the coordination bean looks for any FSM that is in a final state. When a FSM enters a final state, a boolean flag is set. The coordination bean simply looks for this flag. Any machine in a final state is removed from the active list and discarded. Each coordination event has a lifetime (in timesteps, usually set to 1, set to 1 for message events). The Coordination bean steps through the event list and removes all events who have exceeded their lifetime. That's about it for a pulse.
4/1/99: This Warren scenario and sample agents have NOT been tested with the latest versions of everything. They were all working 4-5 months ago. For examples of this working today, see Brett. This section will be updated with a new sample scenario soon.
The directory GPGP2/agent contains all the files for a simple GPGP agent. This agent will read in any Taems task structure, schedule it using dtc, and attempt to coordinate over any non-local enables relationships (GPGP mechanism 4). The problem solver is located in WarrenProblemSolver.java, and the agent's jar files are GPGP-agent.jar, GPGP-support.jar, and GPGP-myApplet.jar. This agent was compiled with the two scripts in the GPGP/fsmcc directory, GPGP4init, and GPGP4resp.
A sample run can be made using two of these simple GPGP agents, the Interface agent and the Task agent (from the Warren scenario). The grammar files are interface.grammar and task.grammar. The interface agent has a enables relationship from one of its tasks to the Formulate-Plan task of the Task agent. To run this example, simply start the simulator, go into the GPGP/agent directory, and run the agents ("make Interface.html" and "make Task.html").