-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Agent Factory Micro Edition (AFME) is a minimised footprint agent platform developed to enable the creation of intentional agents for mobile devices. It is an open source project and the binaries and source code are available for download from SourceForge. Alternatively, the source code is also available on GitHub. AFME targets the Constrained Limited Device Configuration (CLDC)/Mobile Information Device Profile (MIDP) subset of the Java Micro Edition (Java ME) specification. This user guide explains how use the platform through the use of a number of examples. It should be quite easy to follow, but if you have any difficulties please post the problem to the forum on the Agent Factory SourceForge web site. Feedback is much appreciated.
AFME is loosely based on Agent Factory, a pre-existing framework for the development and deployment of multi-agent systems. AFME differs from Agent Factory in a number of ways. To reduce development time, a short hand language has been created. For those already familiar with Agent Factory, a brief overview of the AFME short hand language is provided. It is recommended that even those familiar with Agent Factory should look at the examples of developing services and modules in this guide because this functionality differs significantly.
AFME is concerned with the development of computationally reflective agents. Computational reflection is a technique that enables a system to maintain meta-information about itself and to use this information to determine its behaviour. In the agent community parlance, this meta-information is commonly referred to as an agent's belief set or an agent's model of the world. Many intelligent agent platforms, including AFME, draw from the folk psychology of the philosopher Daniel Dennett and employ the notion of the intentional stance as a tool for modelling complex systems through the attribution of mental attitudes, such as beliefs and goals, to agents so as to explain and predict behaviour. According to Dennett, there are three different strategies we use when confronted with an object or system, namely the physical stance, the design stance, and the intentional stance.
To predict the behaviour of an entity, according to the physical stance, we use information about its physical constitution along with information about the laws of physics. Suppose I am holding a golf ball and I let go of it and I predict that it will fall to the floor. This prediction based on (1) the mass of the ball and (2) the law of gravity.
With the design stance, we assume that the entity in question has been designed in a particular manner. Our predictions are based on the idea that the entity will behave as designed. When someone turns on an electric fan, they predict that it will behave in a certain manner i.e. the fan will cool down the room. They do not need to know anything about the physical constitution of the fan to make the prediction. Predictions made from the design stance are based on two assumptions (1) that the entity is designed for the purpose that the user thinks it to be designed for and (2) that it will perform as designed without malfunctioning. This does not mean that the design stance is always used for entities that have been designed. The physical stance could be used to predict what would happen to the fan if it were knocked onto the floor or if it malfunctioned, but in most cases there is no need to go to a lower level of granularity.
We can often improve our predictions of the design stance by adopting the intentional stance. When making predictions from this stance, we interpret the behaviour of an entity by treating it as a rational agent whose behaviour is governed by mental attitudes. The intentional stance is adopted where it is useful to do so. This is often the case in situations whereby we do not fully understand the design of the system, for example, when considering living organisms. It is less useful when we do understand the inner workings of a particular system. Suppose, for instance, we apply the intentional stance to a door bell i.e. we imagine that it is a rational agent that reasons about its beliefs and desires and intends to alert us when someone is at the door. This is not particularly useful because we can understand the functionality of a door bell in simpler physical or mechanical terms. In contrast, suppose we wish to explain or prediction the behaviour of a person or complex computer system. In such cases, it is necessary to form a higher level of abstraction in we do not fully understand their inner workings or design. The intentional stance can be applied to anything, but it is more practical to use when it leads to simpler descriptions than would otherwise be available.
The behaviour of agents in AFME is represented using declarative antecedent-consequence rules that determine the conditions under which commitments are adopted and actions are performed. To facilitate this, the conditions are matched against belief sets (meta-information maintained by agents) at periodic points throughout execution.
There are two files that must be downloaded in order to use AFME. A Jar file that contains the functionality for the AFME compiler and a Jar file that contains the functionality for the AFME platform. These files can be downloaded from http://sourceforge.net/projects/agentfactory/files/ and are named comp3_3.jar and afme3_3.jar. In the remainder of this user guide, it is assumed that these Jar files are on the classpath.
AFME is based on Agent Factory. In AFME, a short hand version of the Agent Factory Agent Programming Language 2 (AFAPL2) has been developed to reduce development time and improve efficiency. A cross compiler has been created to convert AFAPL2 to the short hand language and vice versa. AFME expresses an agent's internal state through the mentalistic notions of belief and commitment. In AFME, as with many other intelligent agent platforms, system functionality is delivered through a combination of imperative and declarative code. The imperative functionality is in the form of a set of perceptors and actuators. Perceptors generate beliefs about the agent's state and its environment. Actuators enable agents to affect their environment; they provide the imperative functionality for primitive or atomic actions. Rules that define the conditions under which commitments are adopted are used to encode an agent's behaviour in an agent programming language. In short, perceptors generate meta-information (beliefs) about the system state and the environment. Using this information and its internal declarative rule set, the agent will decide on the plans or actions that need to be performed.
Perceptors, actuators, and rules within AFME are taken to be nondeterministic. By this, we mean that the developer should not program agents on the basis that there is a temporal ordering among perceptors, among actuators, or among rules in terms of their execution time. In this way, the various components are viewed as being independent or atomic. For instance, it is possible to use an actuator developed for one application, within a different application, without including other actuators that it depends upon to change system state or perform some action before it executes. If such a temporal ordering is required, it is encoded explicitly within a plan (see the section on using explicit plans).
AFME agents follow a sense-deliberate-act cycle. The agents are executed at periodic intervals. Four functions are performed when an agent is executed. First, the perceptors are fired and beliefs are updated. Second, the agent's desired states are identified through the use of resolution based reasoning. Third, the agent's intended states are identified. Typically, the agent's desired states will be its intended states, but in certain circumstances a knapsack procedure will be invoked (see the section on resource bounded reasoning). Fourth, depending on the nature of the intended states (commitments) adopted, various actuators are fired.
The following is an example of two AFME rules:
a , b > doX; c , d(?var) > doY(?var);
The letters a and b in the first rule represent two beliefs, belief a and belief b. If an agent adopts these two beliefs, then it will adopt a commitment to doX. In this example, the string doX will map to an actuator or imperative code to perform the functionality required. The truth of the belief sentence on the left hand side of a rule is evaluated, based upon the current beliefs of the agent, using resolution-based reasoning. Resolution-based reasoning is the goal-based querying mechanism that is employed within Prolog interpreters. The result of the query process is either failure, in which case the belief sentence is evaluated to false, or to a set of bindings that cause the belief sentence to be evaluated to true.
If the agent adopts belief c and a belief that matches d(?var), then the doY(?var) commitment will be adopted. Variables are preceded with the ? symbol. So, for instance, both beliefs d(cat) and d(dog) would match with belief d(?var). If belief c was also adopted in this case two commitments would be made, one to doY(cat), the other to doY(dog).
There are two types of beliefs that can be adopted, namely temporal beliefs and current beliefs. Current beliefs last for one iteration of the control cycle. There are two types of temporal beliefs next beliefs and always beliefs. If an always belief is adopted, it will remain in the agent's belief set until it is retracted. If a next belief is adopted, the belief argument of the next predicate will be adopted on the next iteration of the control algorithm. So, for example, if a perceptor causes an agent to adopt a next belief called next(a), the belief a will be adopted on the next iteration of the control algorithm.
There are three ways in which beliefs can be adopted. First, initial beliefs are added by the developer in the agent platform script. Second, beliefs are generated by perceptors. Perceptors are classes written in Java. Third, beliefs are added by actuators. Actuators are classes written in Java. Adopting beliefs is similar to assert in Prolog.
There are three ways in which beliefs can be retracted. Current beliefs are retracted after the agent's desires have been identified within the control algorithm. Next beliefs are retracted after the agent's desires have been identified within the control algorithm. The belief argument to the next belief is added at this point for the next iteration. Always beliefs are retracted either by perceptors or actuators.
Within AFME, if an agent has not adopted a particular belief, the negation of the belief will be true. This should be interpreted as the agent does not have a belief with regard to a particular predicate. Not that the agent believes the predicate to be false. That is, the negation is in relation to the belief set of the agent. So, for example, if Bob has no belief about the location of the ball, Bob does not have a belief that the ball is outside, but Bob also does not have a belief that the ball is not outside. In order to represent the negation of a belief within AFME, the ! symbol is used. So, for instance, if the developer wanted the agent to do something when the belief a is not adopted, it would be encoded as follows:
!a > doSomething;
Note: In the first example of how to use commitment rules above, the doY commitment only took one argument. Commitments can take additional arguments. In addition to actuators and perceptors, agents also contain modules. On an agent platform there will also be agent platform services.
There are two ways in which agents can be created within AFME. The AFME development process can be used. Alternatively, agents can be programmed directly in Java (this is the easiest way to create an agent for the beginner, but it is recommeded that beginners still do the helloworld, perceptors, modules, and services tutorials, which use the AFME development process).
In the AFME development process, a number of different components must be developed in order to build an agent platform. The AFME compiler takes as an input an agent platform script. Its output is one or more file(s) (typically Java files) that conform to class schema templates. The class schema for producing MIDlets for mobile phones and similar devices is just one particular instance. The compiler can be used for producing other types of output files. For instance, deployment templates are used to create MIDlets for SunSPOT wireless sensor nodes without graphical interfaces. Indeed, the AFME compiler can be used to generate code for environments other than MIDP; class schemas are provided for CDC devices.
Agent platform scripts specify the various components and parameters of an AFME platform. These include the number of threads operating in the framework, what agents are to be created, the initial beliefs of the agents, what services are to be created, and what agents are to be started.
In addition to the agent platform scripts, the developer must create the agent design, the actuators, the perceptors, the modules, and the platform services. The agent design is written in the AFME short hand version of AFAPL. This information is used in generating the output files. The remaining components are written in Java. The developer can also write their own templates for generating output code from the compiler.
The output files of the AFME compiler will typically be Java source files. These files are compiled in the usual manner for Java ME CDLD/MIDP. If the developer is using an Integrated Development Environment, they should read the documentation as to how to create a project from pre-existing source files. Support is provided for CDLD/MIDP for both Eclipse, through EclipseME, and Netbeans, through the Mobility pack. Alternatively, the developer can use the Java ME SDK directly. A discussion of IDEs is beyond the scope of this guide.
Note: The Agent Factory plugin for Netbeans does not support the current version of AFME. We are working on this for a future release. At present, if using Netbeans, we suggest that agent platform scripts be compiled from the command line. Once the code has been compiled, create a J2ME project (assuming Netbeans includes the Mobility pack) and run the generated MIDlet code in the usual manner (see the Netbeans/Mobility pack documentation for information/examples on how to run MIDlets).
This example will illustrate how to create a simple agent that periodically prints hello world on the console.
Create a new folder called helloworld. Using a text editor, create the following agent platform script and save it as HelloWorld.af. Place it in the helloworld folder.
package helloworld; platform HelloWorld{ // Create 1 thread on the platform scheduler 1; create Alice helloworld.HelloAgent 1000; add Alice always(sayHello); start Alice; template com/agentfactory/cldc/compiler/MIDlet.template HelloWorldlet com/agentfactory/cldc/compiler/AgentPlatform.template HelloWorldAgentPlatform; }
It should be noted that the integer value specified with the create command indicates response time for the agent. In this case, the response time will be 1000 milli seconds. This means that the agent's control algorithm will be executed once every second.
Using a text editor, create the following agent design file and save it in the helloworld folder. The file should be called HelloAgent.sh
act HelloAct; sayHello>printHello;Using a text editor, create the following actuator and save it in the helloworld folder.
package helloworld; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; public class HelloAct extends Actuator { public HelloAct(AffectManager manager) { super(manager,"printHello"); } public boolean act(FOS action) { System.out.println("Hello World"); return true; } }
Compile the HelloAct actuator in the usual manner (Make sure that the AFME Jar file is on the classpath).
Open a command prompt in windows or terminal in linux / unix. Change directory to where the hello world folder is located. For example, if the path of the helloworld folder on a windows machine is c:\afme\examples\helloworld, change directory to the c:\afme\examples folder.
Execute the following command (make sure that the compiler Jar file is on the classpath).
java com.agentfactory.cldc.compiler.Main helloworld/HelloWorld.af
This command will generate two files. One named HelloWorldlet, the other named HelloWorldAgentPlatform. If you are using an IDE, refresh the helloworld folder so that the newly generated files become visible. Run the MIDlet in the usual manner using either an IDE with Java ME support or the Java ME SDK by itself. The application will periodically print the message Hello World on the console. It should be noted that when working with IDEs, if changes are made to the agent platform script or agent design file and the Java files are regenerated, the Java source folder should be refreshed to ensure the changes have been detected.
Tip: Write a batch file or shell script for the command rather than retyping it every time you wish to recompile.
The above diagram shows the output of the compiler to the console. In this case, the -classpath flag was used rather than the CLASSPATH environmental variable. It is important to remember that the platform script must be compiled from the parent directory, in this case c:\afme\examples rather than c:\afme\examples\helloworld. The folder c:\afme\examples\helloworld, however, could be used to compile the Java source.
It should be noted that when compiling the Java code from an IDE, such as Netbeans, the Jar file for the compiler should not be added to the project, only the Jar file for the AFME platform. The AFME compiler is used to generate Java code, which is then compiled using a standard Java ME compiler; the code for the AFME compiler is not required to compile the generated code.
In AFME, the user is provided with an interface that enables them to inspect the mental state of the agents as they operate. The interface enables the user to step the agent through an iteration of its control process, to pause the agent, and to start the agent. The following diagram illustrates the interface operating in the standard Sun colour phone emulator. The developer can view the beliefs of the agent, the commitments of the agent, and the commitment rules of the agent as it executes.
Although the debugging interface is useful during the development stage, the developer is unlikely to want it included in the final distribution of their application. To create an application that does not include the debugger interface, the developer uses a different template when generating the MIDlet and agent platform for the application. The developer can write their own template or use the already existing deployment templates for the MIDlet, the agent platform, and the migration agent platform. For example, to create a hello world platform without a debugger interface using the pre-existing templates for MIDlet and platform generation, change the hello world platform script to the following and ensure that the template files are added to the classpath when compiling.
package helloworld; platform HelloWorld{ // Create 1 thread on the platform scheduler 1; create Alice helloworld.HelloAgent 1000; add Alice always(sayHello); start Alice; template com/agentfactory/cldc/compiler/Deploylet.template HelloWorldlet com/agentfactory/cldc/compiler/DeployPlatform.template HelloWorldAgentPlatform; }
If the developer wishes to create their own debugger interface, they may do so by writing a new template and associated Java code.
This example illustrates how to use perceptors in AFME. Perceptors are Java objects that generate beliefs. Follow the procedure described in the HelloWorld example for compiling the agent platform script and Java code. The following is the agent platform script.
package perceptors; platform Perceptors{ // Create 1 thread on the platform scheduler 1; create Alice perceptors.PerceptorsAgent 1000; start Alice; template com/agentfactory/cldc/compiler/MIDlet.template Perceptorslet com/agentfactory/cldc/compiler/AgentPlatform.template PerceptorsAgentPlatform; }
The following is the agent design for the PerceptorsAgent (Remember to save this in a file called PerceptorsAgent.sh)
act PrintAct; per FirstPer; perceived(?val) > print(?val);
The question make variable in the perceived belief indicates that the argument is a variable. The following is the code of the FirstPer perceptor.
package perceptors; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; public class FirstPer extends Perceptor{ int x; public FirstPer(PerceptionManager manager){ super(manager); x=0; } public void perceive(){ adoptBelief("perceived("+x+")"); x++; } }
Compile FirstPer in the usual manner. On each iteration of the control algorithm, the perceive method of the FirstPer perceptor will be fired. Each time this occurs, the variable x will be incremented. So for instance on the first iteration the agent will adopt the belief perceived(0). On the second iteration the agent will adopt perceived(1) and so on for every subsequent iteration.
The following is the code for the PrintAct actuator.
package perceptors; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; public class PrintAct extends Actuator { public PrintAct(AffectManager manager) { super(manager,"print(?arg)"); } public boolean act(FOS action) { System.out.println(action.next()); return true; } }
Compile the PrintAct actuator in the usual manner. The PrintAct actuator will print its argument out to the screen. In this example if the belief perceived(?val) is adopted, then the agent will fire the act method of the PrintAct actuator. This behaviour (the link between what is perceived and what action is to be taken) is specified in the agent design. The val variable in the perceived belief is applied to the val placeholder in the commitment.
To obtain the argument in the act method the action.next() method is called. This method returns a FOS object that represents val. In this example the code will print out 0 on the first iteration, 1 on the second iteration and so on for all subsequent iterations.
Modules are used when a shared object must be accessible either between actuators and perceptors, among perceptors, or among actuators. For instance condsider the case when a perceptor must perceived the state of an object and an actuator must update the state of the same object. Actuators and perceptors do not contain direct object references to each other to increase decoupling but they must still have some way of passing information to and from the same shared object. This is facilitated through the use of modules.
This example illustrates how to use modules in AFME. Follow the procedure described in the HelloWorld example for compiling the agent platform script and Java code. The following is the agent platform script.
package modules; platform Modules{ // Create 1 thread on the platform scheduler 1; create Alice modules.ModulesAgent 1000; start Alice; template com/agentfactory/cldc/compiler/MIDlet.template Moduleslet com/agentfactory/cldc/compiler/AgentPlatform.template ModulesAgentPlatform; }
The following is the agent design for the modules agent (save this in a file called ModulesAgent.sh).
act ModAct; per ModPer; mod myMod=MyModule; modState(10) > updateMod(20); modState(20) > updateMod(30); modState(30) > updateMod(10);
In this agent design when an integer variable in the module is equal to 10 it is updated to 20. When it is 20 it is then updated to thirty. When it is third is changed back to then again the process repeats itself. Each step takes place on in a different iteration of the control algorithm.
The following is the code for the modules perceptor.
package modules; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; import com.agentfactory.cldc.logic.FOS; public class ModPer extends Perceptor{ PerceptionManager m; public ModPer(PerceptionManager manager){ super(manager); m=manager; } public void perceive(){ FOS fos=m.perManage("mymodule",0); String s=fos.toString(); adoptBelief("modState("+s+")"); } }
To be able to obtain information from a module a perceptor must use a perception manager. The perceptor calls the perManage method of the manager. This method obtains information from a particular module. There will sometimes be a number of modules specified within an agent design. To specify which module the manager will obtain information from the manager must give the name of the module. In this case the name is mymodule. Modules will sometimes contain several pieces of information. To indicate what data the perceptor requires a number must be specified. In this case the number is 0.
The following is the code for the module actuator.
package modules; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; public class ModAct extends Actuator { AffectManager m; public ModAct(AffectManager manager) { super(manager,"updateMod(?arg)"); m=manager; } public boolean act(FOS action) { FOS arg=action.next(); m.actOn("mymodule",0,arg); System.out.println(arg); return true; } }
To be able to send information to a module an actuator must use an affect manager. The actuator calls the actOn method. Multiple modules may be present so the actuator must specify the name of the module that is to be used. It will sometimes be possible to perform a number of different actions on the module. The id number of the action must be specified to indicate which one to perform. In this case the action id is 0. The third argument to the actOn method takes a FOS object. This object represents the information that is to be sent to the module. In this case, it will be the argument was applied to updateMod(?arg).
In this example once the actOn method is called the actuator subsequently prints what was sent to the module. The message printed will initially be 10, then 20, then 30, and the cycle will repeat for all subsequent iterations.
The following is the code for MyModule module.
package modules; import com.agentfactory.cldc.AgentName; import com.agentfactory.cldc.Module; import com.agentfactory.cldc.logic.FOS; import com.agentfactory.cldc.logic.MalformedLogicException; public class MyModule extends Module{ int state; public MyModule(AgentName name){ super("mymodule"); state=10; } public FOS processPer(int id)throws MalformedLogicException{ if(id==0){ String s=String.valueOf(state); return FOS.createFOS(s); }else return null; } public FOS processAction(int id, FOS data) throws MalformedLogicException{ if(id==0){ String s=data.toString(); state=Integer.parseInt(s); } return null; } }
Compile the Java code in the usual manner. Notice that in the constructor of the name is passed to the super constructor, this enables the module to be identified by the affect and perception managers. The module class contains two mandatory methods that the developer must implement, processPer and processAction. These methods specify what occurs in the module when perManage and actOn methods are called within Perceptors and Actuators respectively. Perceptors and actuators cannot call these methods directly and must use the managers.
It should be noted that the reason for the syntax name=ModuleClass in the agent design script is for backwards compatibility with Agent Factory. In most cases, the compiler will ignore the name specified, so both 'myMod=MyModule' and 'mymodule=MyModule' will work. The only case in which the compiler uses the name is when creating a GUI module. In which case, the name specified must be gui (see the example on creating GUI modules). In short, in most cases, any name can be specified for the module in the agent design in that it is legacy from the Agent Factory syntax and it will not be used to identify the module; the name specified in the code in the constructor of the module class will be used for this purpose.
In this example when the processPer method is called (this will be triggered by the perManage method of the perceptor) the method will initially convert the integer state to a string. Using this string representation a FOS object is created. The FOS is then return. On receiving the FOS, the perceptor creates and adopts the appropriate belief. When the processAction method is called (this will be triggered by the actOn method of the actuator) the FOS passed to the method will be converterd to a string. It is then converted to an integer and the state variable is updated. The method will subsequently return null.
It should be noted that if a module contains more that once piece of information that can be perceived or more than one action that can be performed, the action id must be used to distinguish the required behaviour.
Tip: Rather than using constants directly, such as 0 and the name mymodule in this example, it is good programming practice to create a separate class with final public static attributes. The constants can then be reference by variable name. If an alteration is subsequently required, it only has to be performed once.
Consider the case when the developer does not know how many arguments will be passed to a particular predicate in the agent design. For instance, imagine that the user was presented will a list and could select any number of items from the list and that a belief was to be generated to represent the selected items. The use of FOS objects in Agent Factory/AFME enables agents to deal with such constructs.
In Agent Factory / AFME, the functor attribute of an FOS object is often used to distinguish between different types of arguments. This example illustrates how to use the functor in AFME.
The following is the agent design for the arguments example. Follow the procedure described in the HelloWorld example for compiling the agent platform script and Java code.
package arguments; platform Arguments{ // Create 1 thread on the platform scheduler 1; create Alice arguments.ArgumentsAgent 1000; start Alice; template com/agentfactory/cldc/compiler/MIDlet.template Argumentslet com/agentfactory/cldc/compiler/AgentPlatform.template ArgumentsAgentPlatform; }
The following is the agent design for the arguments agent (save in a file called ArgumentsAgent.sh).
act ArgAct; per ArgPer; hotels(?h) > processHotels(?h);
The following is the arguments perceptor.
package arguments; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; public class ArgPer extends Perceptor{ int x; public ArgPer(PerceptionManager manager){ super(manager); x=0; } public void perceive(){ StringBuffer belief=new StringBuffer("hotels("); if(x==0){ belief.append("goodHotels(hotelA,hotelB,hotelC)"); }else if(x==1){ belief.append("badHotels(hotelC,hotelD)"); }else if(x==2){ belief.append("goodHotels(hotelE,hotelF,hotelG,hotelH)"); } belief.append(')'); adoptBelief(belief.toString()); x++; x%=3; } }
This perceptor will generate different beliefs on different iterations. This has been modelled through the use of the x variable and the mod operator. All of these beliefs will match with hotels(?h) and thus the actuator will be fired in each instance.
The following is the code for the arguments actuator.
package arguments; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; public class ArgAct extends Actuator { public ArgAct(AffectManager manager) { super(manager,"processHotels(?arg)"); } public boolean act(FOS action) { FOS fos=action.next(); if(fos.functorEquals("goodHotels")){ System.out.println("These are good hotels"); for(FOS f=fos.next();f!=null;f=fos.next()){ System.out.println(f); } }else{ System.out.println("These are bad hotels"); for(FOS f=fos.next();f!=null;f=fos.next()){ System.out.println(f); } } System.out.println(); return true; } }
In this example, the actuator first checks whether the hotels argument represents good hotels or bad hotels. There is no getFunctor method in AFME. Instead, the functorEquals method is used. After distinguishing the type of list, the names of the hotels are printed. The next method of the FOS class returns the next argument. In this example, the for loops will continue to print the hotel names until there are no more hotel arguments. That is, the actuator can handle variable length lists. If the developer wishes the reset the list, they use the reset method.
Modules represent a means of enabling actuators and perceptors to send information to and receive information from shared objects. A module's scope is only within a single agent. To enable multiple agents to access a shared object through their actuators and perceptors a platform service must be used. The message transport service is an instance of a platform service. This example illustrates how a developer can implement a service that is shared among local agents.
The following is the agent platform script for the services example. Follow the procedure described in the HelloWorld example for compiling the agent platform script and Java code.
package services; platform Services{ // Create 1 thread on the platform scheduler 1; service MyService; create Alice services.ServicesAgent 1013; create Bob services.ServicesAgent 997; add Bob always(update(5)) , begin; add Alice always(update(10)); start Alice, Bob; template com/agentfactory/cldc/compiler/MIDlet.template Serviceslet com/agentfactory/cldc/compiler/AgentPlatform.template ServicesAgentPlatform; }
In this example, both Alice and Bob are services agents. Notice in the platform script the additional line that specifies the name of the service class. The name in this case is MyService. The following is the agent design for the services agent (save this as ServicesAgent.sh).
act UpdateAct; per ServPer; begin, update(?val) > upd(?val); servVal(?val) , update(?val) > upd(?val);
The following is the code for the services perceptor.
package services; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; import com.agentfactory.cldc.logic.FOS; public class ServPer extends Perceptor{ PerceptionManager m; public ServPer(PerceptionManager manager){ super(manager); m=manager; } public void perceive(){ FOS fos=m.perManage("myservice",0); String s=fos.toString(); adoptBelief("servVal("+s+")"); } }
This code is somewhat similar to the modules perceptor. Perceptors cannot reference service directly. They must use the perManage method of the perception manager class. The first argument to the manager method is the name of the service. This is required because there will sometimes be several platforms services operating on an agent platform. The second argument identifies what piece of information to obtain from the service as it may store several pieces of information that may be required and access from other perceptors.
The following is the code for the update actuator.
package services; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; public class UpdateAct extends Actuator { AffectManager m; public UpdateAct(AffectManager manager) { super(manager,"upd (?arg)"); m=manager; } public boolean act(FOS action) { FOS arg=action.next(); m.actOn("myservice",0,arg); return true; } }
The code is quite similar to the modules actuator. The actuator cannot access the service directly it must use the actOn method of the affect manager class. This method takes three arguments, the name of the service, the id of the action to be performed, and the FOS to be sent to the service.
The following is the code for the MyService class.
package services; import com.agentfactory.cldc.AgentName; import com.agentfactory.cldc.Service; import com.agentfactory.cldc.logic.FOS; import com.agentfactory.cldc.logic.MalformedLogicException; import com.agentfactory.cldc.scheduler.Scheduler; import com.agentfactory.cldc.Platform; public class MyService extends Service{ int somevar; public MyService(String[] args, Object[] agtNms, Scheduler scheduler, Platform p){ super("myservice"); somevar=0; } public void modifyBinding(Object oldName, Object newName) { } public FOS processPer(AgentName agentName,int id) throws MalformedLogicException{ if(id==0){ String s; if(somevar==5)s=String.valueOf(10); else s=String.valueOf(5); return FOS.createFOS(s); } return null; } public FOS processAction(AgentName agentName, int id,FOS data)throws MalformedLogicException{ if(id==0){ StringBuffer sb=new StringBuffer(); agentName.appendName(sb); System.out.println(sb+" updating "+data); somevar=Integer.parseInt(data.toString()); } return null; } }
Notice that the service name is passed to the super constructor of the service. This enables the service to be identified by the affect and perception managers. The service class has three mandatory methods that the developer must implement, namely the processPer method, the processAction method, and the modifyBinding method. The processPer and processAction methods are basically the same as the processPer and processAction in the module class except that they also take the name of the agent that called them as an argument. This is necessary as several agents can access the same service. If the array of agent names passed to the constructor of the service are not used the modifyBinding method can be left blank. The modifyBinding method is called if an agent name is changed or an agent migrates to a different platform. If the agent migrates or is killed, the new name argument passed to the modifyBinding will be null.
In this example when an agent updates the service a message is printed out to the screen stating which agent updated the service. The data argument is converted to an integer and the somevar variable is updated. When an agent perceives the service, some processing is performed and a value is returned that the perceptor will perceive.
See the tip in the Modules Section for best practice when using constants.
This example will illustrate how to create two agents that talk to each other. This example illustrates two important features of AFME, how to use the message transport service locally and how to create a MIDP interface service. The message transport service is an instance of a platform service (see the previous section) that is packaged with AFME.
The following is the platform script for the chatter example. Follow the procedure described in the HelloWorld example for compiling the agent platform script and Java code.
package chatter; gui ChatterInterface; platform Chatter{ // Number of threads in operation on the platform scheduler 2; service com.agentfactory.cldc.mts.MessageTransportService "localhost" 0000 Chatter SYNC; create Alice chatter.Chatter 803; create Bob chatter.Chatter 991; add Bob chatUp(Alice); start Bob, Alice; template com/agentfactory/cldc/compiler/MIDlet.template Chatterlet com/agentfactory/cldc/compiler/AgentPlatform.template ChatterAgentPlatform; }
Notice the additional line to indicate the name of the MIDP gui class and also the creation of the message transport service.
The following is the agent design file for the chatter agent. Save the agent design in a file called Chatter.sh in the chatter folder.
import com.agentfactory.cldc.Agent; act ChatActuator; chatUp(?agent) > request(?agent,chat); message(request,sender(?agt,?addr),chat) > par(request(?agt,chat),chat(?agt));
Notice at the top of the agent design there is an import command. An import command is similar to the include command in C. It copies the contents of the agent design into the current design so that it appears to be a single agent design. This is useful for it enables the creation of generic agent designs that can be reused in several agents.
In the second commitment rule of the chatter agent design, a parallel explicit plan operator (par) is used. This means that the agent will perform two actions in this iteration.
The following is the code for the chat actuator.
package chatter; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; public class ChatActuator extends Actuator { AffectManager manager; /** Creates a new instance of ChatterActuator */ public ChatActuator(AffectManager manager) { super(manager,"chat(?name)"); this.manager=manager; } public boolean act(FOS action) { manager.actOn(ChatterConstant.CHATTER, ChatterConstant.CHAT,action.next()); return true; } }
Notice that the constants (see the tip in the Modules Section ) passed to the actOn method are specified using variable names. The following is the code for the ChatterConstant class.
package chatter; public class ChatterConstant { public static final String CHATTER = "chatter"; public static final byte CHAT = 0; }
The name chatter and the number 0 could of course be written directly but it is better programming practice to write code it in this manner since if an alteration is to be made it will be localised to this class.
The following is the code for the ChatterInterface.
package chatter; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Display; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.TextBox; import javax.microedition.midlet.MIDlet; import com.agentfactory.cldc.AgentName; import com.agentfactory.cldc.Service; import com.agentfactory.cldc.UserInterface; import com.agentfactory.cldc.logic.FOS; import com.agentfactory.cldc.logic.MalformedLogicException; /** * * @author Conor Muldoon */ public class ChatterInterface extends Service implements UserInterface,CommandListener{ TextBox box; Display display; Displayable agt; public ChatterInterface(String[]args,Object[]agentName, MIDlet midlet,Displayable displayable){ super(ChatterConstant.CHATTER); agt=displayable; box=new TextBox("Conversation","",200,0); if(displayable!=null){ display=Display.getDisplay(midlet); Command agents=new Command("Agents",Command.ITEM,1); box.addCommand(agents); box.setCommandListener(this); } } public void display(){ display.setCurrent(box); } public void modifyBinding(Object oldName, Object newName){ } public FOS processPer(AgentName name,int perceptionID) throws MalformedLogicException{ return null; } public void run(){ } public void commandAction(Command command,Displayable dis){ display.setCurrent(agt); } public FOS processAction(AgentName name, int actionID,FOS data) throws MalformedLogicException{ StringBuffer buf=new StringBuffer(box.getString()); buf.append("\nChattering to: "); buf.append(data); if(buf.length()>200) box.setString("Chattering to: "+data.toString()); else box.setString(buf.toString()); return null; } }
The chatter interface is a local platform service. The processPer and processAct method should be written in the usual manner. It also implements the UserInterface and CommandListener interface. The UserInterface interface requires the developer to implement a display method. The CommandListener is a MIDP interface. CommandListener is documented in numerous MIDP resources and a discussion of it is beyond the scope of this user guide.
When creating an interface using the gui command and the standard templates. The following arguments will be passed to the constructor, String[]args, Object[]agentName, MIDlet midlet, Displayable displayable. User defined interfaces must accept these arguments.
This example differs from the previous examples in that it uses the MIDP user interface functionality rather than the console. To view the chatter text box, the user must select the application option from the list displayed when the MIDlet begins operating.
In the chatter example, the user interface created was a local platform service that several agents could access. This section illustrates how to create an interface as a module within a single agent. In this example, the same chat actuator and agent design will be used. Add the following line to the chatter agent design and save the file.
mod gui=ChatterModule;
When creating a GUI module, the name specified in the agent design after the mod keyword must be gui, otherwise it will be treated as a standard module. The reason GUI modules must be treated differently is due to the manner in which the MIDP interface model works. Stand alone interfaces cannot be created. A MIDlet must be used. Comment out the gui line in the Chatter agent platform script. The following is the code for the chatter GUI module.
package chatter; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Display; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.List; import javax.microedition.lcdui.TextBox; import javax.microedition.midlet.MIDlet; import com.agentfactory.cldc.AgentName; import com.agentfactory.cldc.Module; import com.agentfactory.cldc.UserInterface; import com.agentfactory.cldc.logic.FOS; import com.agentfactory.cldc.logic.MalformedLogicException; import com.agentfactory.cldc.scheduler.Scheduler; /** * * @author Conor Muldoon */ public class ChatterModule extends Module implements UserInterface,CommandListener{ TextBox box; Display display; Displayable agt; public ChatterModule(AgentName name,MIDlet midlet, List l,Scheduler sch){ super(ChatterConstant.CHATTER); agt=l; box=new TextBox("Conversation","",200,0); if(l!=null){ display=Display.getDisplay(midlet); Command agents=new Command("Agents",Command.ITEM,1); box.addCommand(agents); box.setCommandListener(this); } } public void display(){ display.setCurrent(box); } public FOS processPer(int perceptionID) throws MalformedLogicException{ return null; } public void run(){ } public void commandAction(Command command,Displayable dis){ display.setCurrent(agt); } public FOS processAction(int actionID,FOS data) throws MalformedLogicException{ box.setString("Chattering to: "+data.toString()); display.setCurrent(box); return null; } }
Recompile the agent platform script and then compile the Java code in the usual manner .
The message transport service has been designed to periodically contact an external message server to receive incoming messages and to send outgoing messages. The reason for this is that in Ireland 3G and GPRS service providers block incoming socket connections to mobile devices, therefore a mobile device cannot operate a server to receive incoming connections. If there is not a firewall present in your network, and it is possible to make direct socket connections from source to destination, use the peer to peer message transport service, otherwise use the message transport service discussed in this section.
To use the external message server, download the message server Jar file from SourceForge. The name of the required jar file is afme_servX_X.jar. Download the Xerces jar file (xercesImpl.jar), as this is required to run the server. Put both Jar files on the path or use the -classpath flag when issuing the following command.
java com.agentfactory.mts.mpp_server.MPPServer 3333 8 5555The arguments passed to the MPPServer specify the port number the server should run on for receiving messages from embedded AFME platforms, the number of threads the server should use, and the port number for receiving messages from standard FIPA platforms.
In the previous Chatter example the message transport service was used locally. The following indicates how the message transport service could be set up to contact remote platforms.
service com.agentfactory.cldc.mts.MessageTransportService "navanman.ucd.ie" 3333 Chatter SYNC 1000;
In this example the first argument, after the message transport service class is specified, is the host address. The next argument is the port number. The next argument is the name of the platform. The next argument specifies that the platform is operating in synchronous mode. The next argument specifies the response time at which the message transport service will contact the message server. This values is specified in milli seconds. In this case, the message transport service will contact the message server once every second.
It should be noted that the agent's address from an external perspective will be that of the FIPA message server. So in this example the agents created on the platform will have the address (http://navanman.ucd.ie:5555/acc) not (http://navanman.ucd.ie:3333/acc). The service connects to port 3333 to receive incomming mail but external agents send messages to port 5555. The reason for this is that two different protocols are being used. This is transparent from the external agent's point of view. External agents just communicate with the Message Server as if contacting another FIPA compliant platform directly. The following indicates how to send the message hello to an Agent called Alice that is operating on a platform with the message server address of http://navanman.ucd.ie:5555/acc.
inform(agentID(Alice, addresses("http://navanman.ucd.ie:5555/acc")),hello)
To send and receive messages to and from an AFME platform form a standard Agent Factory platform, the standard HTTP message transport service is used on the standard platform. The messages are sent from the standard platform to the FIPA address of the message server that the AFME platform is using.
This example illustrates how to use the message transport service and the intermediary message server in AFME. The application is similar to the previously developed Chatter application, but with agents on different platforms. The following is the platform script for Alice’s platform.
package netchat; gui chatter.ChatterInterface; platform Aliceplat{ // Number of threads in operation on the platform scheduler 2; service com.agentfactory.cldc.mts.MessageTransportService "navanman.ucd.ie" 3333 Aliceplat SYNC 1007; create Alice chatter.Chatter 991; start Alice; template com/agentfactory/cldc/compiler/MIDlet.template Aliceplatlet com/agentfactory/cldc/compiler/AgentPlatform.template AliceplatAgentPlatform; }
Store the script in a file called Aliceplat.af in a folder called netchat. The following is the agent design for the Chatter agent.
import com.agentfactory.cldc.Agent; act chatter.ChatActuator; chatUp(?agent) > request(?agent,chat); message(request,sender(?agt,?addr),chat) > par(request(?agt,chat),chat(?agt));
The design is the almost the same as that used in the chatter example except that the package is specified when declaring ChatActuator. Modify the original Chatter design accordingly. It should be noted that there is no need to change it back when using the previous example as it will still work. It’s just that actuators’, perceptors’, and modules’ packages must be specified in the design if the design is to be reused in another application or is to be accessed from another package. When compiling this example, ensure that the code for Chatter is on the classpath.
Tip: When writing designs that are to be reused, specify the packages for all components.
The following the platform script for Bob’s platform.
package netchat; gui chatter.ChatterInterface; platform Bobplat{ // Number of threads in operation on the platform scheduler 2; service com.agentfactory.cldc.mts.MessageTransportService "navanman.ucd.ie" 3333 Bobplat SYNC 1007; create Bob chatter.Chatter 991; add Bob chatUp(agentID(Alice,addresses("http://navanman.ucd.ie:5555/acc"))); start Bob; template com/agentfactory/cldc/compiler/MIDlet.template Bobplatlet com/agentfactory/cldc/compiler/AgentPlatform.template BobplatAgentPlatform; }
Compile the code for the platforms in the usual manner. When executing the code, start the message server first as illustrated previously. Subsequently, start Alice’s platform and then Bob’s.
It should be noted that, in this example, the hostname navanman.ucd.ie should changed to the hostname or IP address of the computer that the message server is operating on. Additionally, make sure that either the IP address is always used in both scripts or the fully qualified hostname is always used. The reason for this is that there is a bug in the current version, whereby the host name in the address of an agent will not be matched with its corresponding IP address in a comparison performed in the server. For example, if the chat up belief in Bob's script were changed to chatUp(agentID(Alice,addresses("http://193.1.132.9:5555/acc"))) where 193.1.132.9 was the IP address of navanman.ucd.ie and if the address argument passed to the message transport server in Alice's script remained as navanman.ucd.ie, the code would not work.
Notice that, when adding the chatUp belief to Bob, Alice’s address is specified as http://navanman.ucd.ie:5555/acc. That is, Alice's FIPA address is used. Also notice that the address is specified within quotes. If an argument is specified within quotes in AFME, the argument is taken to be that within the quotes. This is useful for encoding addesses, such as http://navanman.ucd.ie for example. If quotes were not used in such a case, the text after // would be taken as a single line comment. It should be noted that the AFME compiler removes the quotes before it applies the argument to a template. If the developer wishes to encode a quote within an argument, they use an escape sequence. For example, add Bob initialStartMillis(“\”+System.currentTimeMillis()+\””); could be used to add an initial belief to an agent called Bob regarding the start time of the application. Indeed, any static method could be used in creating a belief in such a manner provided it didn’t have a void return type.
Explicit plans are modelled in AFME in a similar manner to Agent Factory. In AFME, they are represented as par, seq, or, xor rather than PAR, SEQ, OR, XOR. This example will illustrate the two most useful explicit plans, namely par and seq. For a discussion of the other types of plan, see the standard Agent Factory documentation.
Plans improve the efficiency of the software. Consider the following two commitment rules.
a,b,c > doX; a,b,c > doY;It is clear from this example that the belief sequences to the left of both commitment rules are identical be the behaviour encode is different (different actuators will be fired). Rather than have the developer encode the information twice and the system evaluate it twice and explicit parallel plan can be used. The following illustrates how this is done.
a,b,c > par(doX,doY);The sequential plan operator (seq) is used when a number of operations must be performed in a sequence. Consider the following commitment rule.
a,b,c > seq(doX,doY,doZ);If beliefs a, b, and c are adopted, then the action doX will be performed on the current iteration, followed by the doY action on the next iteration. On the third iteration the doZ action would be performed.
There are two actuators that are quite useful for dealing with temporal beliefs and in particular always beliefs. The actuators are packaged within AFME. The following illustrates how they could be used within an agent design.
act com.agentfactory.cldc.RetractActuator, com.agentfactory.cldc.AdoptActuator; someBelief > retractBelief(always(anotherBelief)); diffBelief > adoptBelief(always(aBelief));
The retract actuator removes a belief. The adopt actuator adds a belief. It should be noted that these actuators are specified within the abstract agent design com.agentfactory.cldc.Agent. If this design is imported, there is no need to specify them.
The adopt and retract actuators have been specifically designed to enable agents to adopt and retract beliefs at a declarative level. If developer wishes an application specific actuator to adopt or retract a belief at an imperative level, they use the adoptBelief(FOS bel) and retractBelief(FOS bel) methods of the Actuator class. It should be noted that actuator beliefs are not adopted or retracted on the current control cycle since actuator execution at a logical level is nondeterministic. That is, the order in which actuators or plans are executed to achieve a desire in the current cycle is irrelevant at a logical level and the actuators must be encoded as such. There are no causal relations among or between desires, plans, or atomic action unless explicitly specified through the use of a sequential plan operator therefore beliefs will only be added to the agents current belief set after all of the commitments in the current cycle have been executed.
To create an initial commitment in AFME, which will be added to the agents commitments when an agent begins to operate, the developer writes a commitment rule without any beliefs to the left of the implication. This useful to bootstrap processes at start up. For example, the following is the design of an agent that will adopt an initial commitment to display Hello when it begins operating.
act com.agentfactory.cldc.PrintActuator; >print(Hello);
More recent additions to the Agent Factory Agent Programming Language (AFAPL) provide support for dynamic role adoption. This functionality is also supported within AFME (see the Short Hand Language Section). In the original approach, if developers wished to specify a role that could be adopted by several heteorgeneous agents, the only option they had available was the use of the import keyword. With this approach, the role would be specified abstractly in an agent design and the heteorgeneous agents would extend the functionality. This increases reuse within the system but it is an unstructured approach. In certain situations multiple inheritance would have to be used, which leads to complex ancestral hierarchies. The extended agents become coupled to the agents they inherit from. Whereas the use of the import keyword represents agent-based inheritance, dynamic role adoption more closely resembes the concept of composition.
With this approach, the developer specifies certain triggers for the adoption of a particular role. These triggers are in the form of beliefs. When the belief occurs, the agent adopts all of the commitment rules specified within the role. At some later stage the role will be dropped and all of the commitment rules removed. This improves the efficiency of the reasoning algorithm in that the commitment rules are only evaluated while the role is active. In many cases it also leads to the development of more maintainable agent designs.
The system also provides support for role templates. Role templates enable the developer to increase the abstraction level for the specification of roles. Variables within the trigger are applied to the template to construct a particular role instance. This further increases reuse within the system.
Thus far in the user guide the examples only contained commitments that took one argument, namely the identifier of the plan or action. In AFAPL2, commitments take additional arguments. This section provides a summary of the AFME short hand language and illustrates how other arguments can be passed to commitments.
The AFME short hand language was developed to reduce development time. The semantics of the AFME shorthand language are identical to that of AFAPL2, but the short hand language is faster to program. Consider the following commitment rule:
BELIEF(z) & BELIEF(x) & BELIEF(y) => COMMIT(Self,Now,BELIEF(true),doSomeThing);
In the short hand language, whether something is a belief or a commitment is determined from its context rather than using the strings BELIEF or COMMIT. If something is to the left of the implication, it is a belief, but if it is to the right, it is a commitment (maintenance conditions are beliefs to the right of the implication but they can also be determined from context i.e. they are the third argument of a commitment). The short hand parser makes use of the fact that the majority of commitments take the arguments Self,Now,BELIEF(true). These arguments are taken as defaults and do not have to be specified in the design. If the developer wishes to specify alternatives to the default values, they still have the option to do so. The previous rule could be written in short hand as follows and then generated through the use of the parser.
z,x,y>doSomeThing;
To specify arguments to a commitment that differ from the default values, a syntactic predicate is used in the parser. If two arguments are specified to the right of the implication, they are taken to be the maintenance condition and the action or plan. Consider the following short hand description.
someBelief>otherBelief,doAction;
This is equivalent to the following.
BELIEF(someBelief) => COMMIT(Self,Now,BELIEF(otherBelief), doAction);
If three arguments are specified in the commitment, the first belief is temporal, the second the maintenance condition, and the third is the action or plan.
someBelief>+10,otherBelief,doAction;
is equivalent to
BELIEF(someBelief) => COMMIT(Self,+10,BELIEF(otherBelief), doAction);
If all four arguments are specified, they take their relative positions as specified in standard AFAPL.
someBelief>SomeAgent,+10,otherBelief,doAction;
is equivalent to
BELIEF(someBelief) => COMMIT(SomeAgent,+10,BELIEF(otherBelief), doAction);
If the user wishes to specify the temporal or agent arguments only and leave the other arguments as defaults, all following arguments must still be specified as in the following example.
someBelief>SomeAgent,Now,true,doAction; someBelief>+10,true,doAction;
The parser is sensitive to the syntactic context. The number of arguments dictates what the relative ordering of the arguments represent. If one argument is specified, it is an action or plan. If two arguments are specified, the first argument is a maintenance condition and the second argument is a plan or action. If three arguments are specified, the first argument is temporal reference, the second argument is a maintenance condition and so on.
To further reduce development time, certain AFAPL2 keywords are shortened and written in lower case letters rather than upper case. Consider the following agent design.
/* Multi-line comment */ ACTUATOR DoDActuator; ACTUATOR DoFActuator; PERCEPTOR APerceptor; PERCEPTOR AnotherPerceptor; LOAD_MODULE someMod SomeModule; IMPORT SomeAgentDesign; BELIEF(first) & BELIEF(second) & BELIEF(third) => COMMIT(Self,Now,BELIEF(true),doD); // Single line comment ROLE someRole{ TRIGGER BELIEF(g); BELIEF(e) => COMMIT(Self,Now,BELIEF(true),PAR(doF,doG)); BELIEF(e) => COMMIT(Self,Now,BELIEF(p), deactivateRole(someRole)); BELIEF(x) & BELIEF(y) => COMMIT(Self,+10,BELIEF(true),doZ); } BELIEF(act) => COMMIT(Self,Now,BELIEF(true),doR);
This design was generated from the following short hand description.
/* Multi-line comment */ act DoDActuator; act DoFActuator; per APerceptor; per AnotherPerceptor; mod someMod=SomeModule; import SomeAgentDesign; first,second,third>doD; // Single line comment role someRole{ trigger g; e>par(doF,doG); e>p,deactivateRole(someRole); x,y>+10,true,doZ; } act>doR;
A reverse compiler has been developed to convert standard AFAPL agent designs into the short hand description. This prevents developers having to rewrite legacy agent designs in the short hand.
In the previous example the keywords per and act were used twice. It should be noted that the same code could be represented by
act DoDActuator, DoFActuator; per APerceptor, AnotherPerceptor;
Actuators and perceptors may be delimited by a comma and whitespace is irrelevant.
To use the cross compiler, the compiler Jar file must be on the class path. Execute the following to convert from an AFAPL2 file to a short hand file.
java com.agentfactory.compiler.sh.Main AgentDesign.afapl2Change the name from AgentDesign.afapl2 to the name of the Design in question.
To convert from a short hand file to and an AFAPL2 file issue the following command.
java com.agentfactory.compiler.sh.Main AgentDesign.shAgain change the name. The cross compiler recognises the extensions and will determine what type of conversion to perform automatically.
The short hand language works because BELIEF and COMMIT are not needed to distinguish beliefs and commitments from other constructs. One argument against the use of the short hand language might be that it reduces the extensibility of the language because it removes potential distinguishing predicates. If a new predicate were introduced to the language, there must be some way to differentiate it and the pre-existing constructs. The short hand parser deals with this issue through the use of the word keyword. Consider the following rule where the concept of knowledge is introduced to the language.
KNOW(s) => COMMIT(Self,Now,BELIEF(true),doY);
One can image why knowledge might be introduced to AFAPL. The difference between knowledge, a belief, and a guess is merely how sure we are about the truth of a particular proposition. For instance, I might either know where someone is, have a belief about where they are, or guess their location; the difference between the constructs used reflects variable levels of certainty. We can never truly know anything without at least some doubt except, perhaps, in the case of pure mathematics. There is no clear boundary between knowledge and a belief or a belief and a guess. We must consider what is reasonable, but this is a gray area and is related to how we quantify certainty. Different levels of certainty about the truth of propositions prescribe different behaviour or functionality. The more certain we are the more proactive we will be because the risk of being mistaken will be lower. If I have to meet someone and I know where they are, I will not ring them to find out their location, but if I guess where someone is, I might ring just to check and potentially save an unnecessary journey. How an agent behaves, or the actions it performs, when it adopts meta-information, (be it a belief, a guess, or a known fact) can often be encoded by the application developer within the design. The developer will often know a priori of the certainty of the proposition adopted. For instance, if the developer is creating a user interface agent, the agent will know if the user has clicked a button, but if a WSN application is being developed, the information adopted will be less certain.
In the short hand parser, all predicates to the left of the implication are treated as beliefs, thus if a type of construct were introduced, such as a knowledge predicate, the language must have some mechanism for dealing with it. Otherwise, if the cross compiler were used to generate AFAPL2 code, it would threat it as a belief. That is, it would generate BELIEF(KNOW(?x)), rather than just KNOW(?x). The following outlines how the word keyword is used to encode the unknown construct given in the previous commitment rule.
word KNOW(?fos); KNOW(s)>doY;
The word KNOW(?fos) only has to be defined once and can subsequently be used anywhere in the agent design.
AFME is consistent with the Belief, Desire, and Intention (BDI) model of agency. The BDI model acknowledges that agents are resource bounded and will be unable to achieve all of the desired states even if their desired states are consistent. An agent must fix upon a subset of desire states that it commits resources to achieving. In AFME, agents adopt the subset of commitments that maximise their utility with respect to their finite resources.
To use the resource bounded reasoning capabilities of AFME, an additional argument must be specified when creating an agent in the agent platform script. This additional argument specifies the initial resource constraints of the agent. In the following example, the agent Bob is given an initial resource constraint of twenty.
create Bob BobsDesign 1000 20;
In using the resource bounded reasoning capabilities, an additional two arguments are passed to a commitment. These values represent the cost and benefit of adopting the commitment. The values can be hard coded by the developer, but they will usually be determined by a perceptor and specified in a similar manner to the second rule in the following example.
a >doA'10,5; b(?val1,?val2) > doB'?val1,?val2;If the additional arguments to the commitment are not specified, they will take default values of 1 and 0 for benefit and cost respectively. The agent will adopt the commitment for free (under the conditions specified by the belief sentence the commitment will always be adopted provided the maintenance condition evaluates to true). In this respect, this approach is consistent with AFAPL2.
The intention selection process of AFME is equivalent to the 0-1 knapsack problem. Given n items, with corresponding values and weights, the knapsack problem concerns the packing of some of these items in a knapsack of a specified capacity C such that the profit sum of the included items is maximised. This is equivalent to the problem of an agent attempting to adopt the subset of commitments that maximise its utility with respect to its finite resources. It is called the 0-1 version of the knapsack problem because there are no fractions. The whole item must be either included in the knapsack or left out. Similarly, an agent cannot adopt half a commitment.
The knapsack problem is NP hard. The solution adopted in AFME uses a dynamic programming approach and operates in pseudo polynomial time. It has a run-time complexity of O(nC). A pseudo polynomial time algorithm will exhibit exponential behaviour, but only when confronted with 'exponentially large' instances. When using the resource bounded feature of AFME, use small numbers rather than big numbers where possible when choosing resource constraints. The running time of the algorithm is not only proportional to the input size but also to the magnitude of the numbers used.
The amount of resources available to an agent will often vary over time. To update the resource constraints in AFME the resource actuator is used. The following illustrates how to use the resource actuator.
act com.agentfactory.cldc.ResourceActuator; resource(?amount) > updateResource(?amount);
The developer should write a perceptor that montitors resources (e.g. battery power of the device). When the value changes beyond a certain thresshold, the perceptor will adopt a belief that will trigger the resource actuator.
In AFME, some commitments will be adopted for a number of iterations of the control algorithm. Over time the benefits and cost of commitments might change. For instance, if someone puts a lot of effort into achieving a commitment and has almost completed it, they will be less likely to drop the commitment. The following illustrates how commitment values can be altered.
act com.agentfactory.cldc.ValuesActuator; x>seq(print(1),print(2),print(3), print(4),print(5))'10,10,printNumbers; values(?benefit,?cost) > updateValues(?benefit,?cost,printNumbers);
Notice that there is an additional argument to the commitment that follows the initial cost and benefit values. This argument is a label that will be used to identify the commitment at a later stage. The developer must write a perceptor that adopt beliefs concerning the benefits and cost of a commitment. Once the values change, a belief will be adopted that will trigger the update values actuator. The update values actuator takes three arguments. The new cost value, the new benefit value, and the label of the commitment.
Belief labeling has been introduced to improve the efficiency of the reasoning algorithm and reduce development time. It is syntactically different from AFAPL, but the underlying semantics are equivalent. The following commitment rules
x , y is somelabel; w > doSomething req somelabel; z > doSomethingElse req somelabel;are an equivalent alternative to
x, y, w > doSomething; x, y, z > doSomethingElse;similarly
z > doSomething req !somelabel;is equivalent to
!x , y , z > doSomething; x , !y , z > doSomething; !x , !y , z > doSomething;
The second case is not logically equivalent to the single commitment rule
z > doSomething;because in that case if both beliefs x and y were adopted the commitment would still be espoused, this is not what we want. In this example, the agent will only ever adopt one commitment because each rule is mutually exclusive. It’s clear, in this case, that considerably less reasoning is required than the original AFAPL approach. Additionally, the developer need only write one line of code rather than three (provided somelabel is already declared). This improves efficiency as the belief sequence is only evaluated once.
Agent designs that contain belief labels may be compiled into a format that is equivalent to the original AFAPL syntax so that the agent can run on the standard platform. When an agent migrates from an AFME environment to a standard environment, it is converted to the alternative format.
Belief labels can be embedded to further reduce redundancy. The somelabel belief set is embedded in the otherlabel belief set in the following example.
x, y is somelabel; w , z is otherlabel req somelabel;
It should be noted that cycles are prohibited. If the developer encodes cyclical dependencies, the compiler will throw an error.
Belief labeling reduces redundant processing. It enables developers to encode common sub sequences of predicates, which are only evaluated once. A depth first search is still used to match variables, but the search space is considerably reduced. A depth first search has a runtime complexity of O(bm), where b is the branching factor (the branching factor is the average number of children) and m is the maximum depth. To evaluate the efficiency of belief labeling we consider the worst case, in which the developer encodes a negated logical and . A negated logical and specifies the conditions under which a commitment will not be desired. Under all other circumstances it will be desired. To model this type of situation with the original approach, the system would exhibit exponential behaviour. The developer would have to write, and the system would have to process, 2n-1 rules for n conditions. With belief labeling, the developer need only write, and the system need only process, 1 rule.
The AFME compiler can alter the response time values specified within a platform script to be prime numbers. This is useful in that reduces synchronisation between agents that have harmonically similar execution periods. To get the compiler to alter the response times to be prime numbers, add the flag –p after the file name is specified. The primes generated are those that have the greatest harmonic difference within a particular threshold or if there are no primes within this range, the prime closest to the original value. The following illustrates how to use the compiler to alter the response time specified in the Hello World example to be a prime number.
java com.agentfactory.cldc.compiler.Main helloworld/HelloWorld.af -pIt should be noted that if all agents have identical response times, the developer need not alter them to be prime numbers, as the AFME will phase shift the agents so as to avoid computational bottlenecks.
In the AFME compiler, the system classpath can be augmented by passing the –cp flag to the Main class. The following illustrates how the –cp flag is used.
java com.agentfactory.cldc.compiler.Main –cp some_jar_file.jar helloworld/HelloWorld.afThe –cp flag directly follows the Main class. This is only really useful when the Main class for the compiler is embedded in a script or batch file or within some other application, otherwise the Java system classpath could be used, such as in the following example.
java –classpath some_jar_file.jar;%CLASSPATH% com.agentfactory.cldc.compiler.Main helloworld/HelloWorld.af
Consider the case in which a batch or shell file had been written. If the user wished to append the classpath, they could pass the -cp flag as an argument to the script and thereby use the compiler class loader along with the standard system class loader. The reason the user might wish to do this is that the script could be generic or used in a number different contexts.
In situations whereby the Main class is embedded in some other tool, such as an IDE, by decoupling the compiler's classpath from the tool's classpath, the need to have the compiler’s classpath entries in global scope is removed.
The following illustrates how to both use the classpath and alter response times to be prime values.
java com.agentfactory.cldc.compiler.Main –cp some_jar_file.jar helloworld/HelloWorld.af –pThe –p flag is the last argument.
The examples thus far discussed in the user guide concerned the development of applications that will operate on a mobile phone or other device that supports MIDP. This section discusses the development of a platform that does not execute in a MIDlet. In this example, the application will be started using a main method. This is useful for the creation of applications that will run on CDC devices, such as the Crossbow Stargate WSN gateway, that do not have graphical interfaces. To create an application that does not operate in a MIDlet, we must use a different template for generating the Java code. The standard template for generating code that contains a main method is com/agentfactory/cldc/compiler/CDCAgentPlatform.template. Consider the HelloWorld example discussed earlier; to create a HelloWorld application that will executed by invoking a main method, change the template used in the code generation process. That is, change the template specification in the agent platform script from
template com/agentfactory/cldc/compiler/MIDlet.template HelloWorldlet com/agentfactory/cldc/compiler/AgentPlatform.template HelloWorldAgentPlatform;to
template com/agentfactory/cldc/compiler/CDCAgentPlatform.template HelloWorldAgentPlatform;Delete the generated HelloWorldlet and HelloWorldAgentPlaform source files if you previously did the original HelloWorld example. Regenerate HelloWorldAgentPlaform source file using the AFME compiler (follow the instructions in the HelloWorld example). Recompile the code. To compile the code either a CDC compiler must be used or a standard compiler along with the Generic Connection Framework (JSR 197). If a standard compiler is used, the source and target must be set to Java version 1.4. When compiling the code and deploying the application, use the AFME platform Jar file that has no dependencies on user interface functionality (afme_nointerX_X.jar) rather than the MIDP AFME platform Jar file. The code can now be deployed to the device in question and can be executed by invoking the main method on the HelloWorldAgentPlatform class.
It should be noted that the developer can pass initial beliefs to agents from the command line when starting the platform by specifying the name of an agent followed by the belief to be added. For instance, if the arguments passed to the main method of the platform were Alice, a, Alice, b, the initial beliefs a and b would be added to Alice.
This section describes how an agent migrates from a source AFME platform to a destination AFME platform. The migration service in AFME operates in a similar manner to the message transport service. It contacts an external server to receive incoming agents. It also uses the server to send outgoing agents. The reason that this approach has been adopted is the same as that for the message transport service; in Ireland, GPRS and 3G service providers block incoming socket connections to mobile phones, therefore an indirect approach must be used. If there is not a firewall present in your network, and it is possible to make direct socket connections from source to destination, use the peer to peer migration service, otherwise use the migration service discussed in this section.
To use this service, the migration Jar file (migrationX_X.jar) must be added to the classpath. The following is the agent platform script for the source platform.
package migration; platform Ireland{ // Number of threads in operation on the platform scheduler 1; service com.agentfactory.cldc.migration.MigrationManager 1007 "socket://navanman.ucd.ie:6060" Ireland; create Alice migration.Sparrow 1000; add Alice always(dest(navanman.ucd.ie:6060:Africa)); start Alice; template com/agentfactory/cldc/compiler/MIDlet.template Irelandlet com/agentfactory/cldc/compiler/MigAgentPlatform.template IrelandAgentPlatform; }
The first argument passed to the migration service represents the period at which the migration service will connect to the migration server to send/receive agents. In this case, the migration service will connect once every 1007 milliseconds. The second argument represents the connection type, the host, and port number of the migration server. Change the address and port to the address and port of where the migration server will be run. The third argument is the name of the platform. Save the source platform script in a file called Ireland.af. The following is the design for the agent that will migrate.
act com.agentfactory.cldc.RetractActuator, com.agentfactory.cldc.migration.MigrateActuator; per TimingPerceptor,TestPerceptor; mod test=TestModule; timeToMigrate, dest(?url) > par(migrate(?url,null), retractBelief(always(dest(?url))));
Save the design in a file called Sparrow.af (packaged in the folder migration). Generate the code for the MIDlet and agent platform in the usual manner. In this example, once the agent adopts the beliefs timeToMigrate and dest(?url), the migrate actuator will be triggered. In this case, the belief always(dest(?url)) is added in the platform script. Change this belief in the platform script to reflect the desired host name and port number.
The agent contains two perceptors, namely the timing perceptor and the test preceptor. The timing perceptor generates the belief timeToMigrate after 10 seconds. Provided the agent has the belief dest(?url), this will cause the agent to migrate to the specified destination. The following is the code for the timing preceptor.
package migration; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; /** * * @author Conor Muldoon */ public class TimingPerceptor extends Perceptor{ long time; /** Creates a new instance of TimingPerceptor */ public TimingPerceptor(PerceptionManager manager) { super(manager); time=System.currentTimeMillis(); } public void perceive(){ if(System.currentTimeMillis()-time>10000){ adoptBelief("timeToMigrate"); } } }
The test perceptor perceives information from the test module. The following is the code for the test perceptor.
package migration; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; import com.agentfactory.cldc.logic.FOS; /** * * @author Conor Muldoon */ public class TestPerceptor extends Perceptor { private PerceptionManager manager; /** Creates an instance of TestPerceptor. * * @param manager the perception manager for the agent. */ public TestPerceptor(PerceptionManager manager){ super(manager); this.manager=manager; } public void perceive() { FOS fos=manager.perManage("test",0); adoptBelief(fos); } }
The test module prints the string “perceiving module” once its processPer(int id) method is called and subsequently returns the belief testing. The following is the code for the test module.
package migration; import com.agentfactory.cldc.AgentName; import com.agentfactory.cldc.Module; import com.agentfactory.cldc.logic.FOS; import com.agentfactory.cldc.logic.MalformedLogicException; public class TestModule extends Module{ public TestModule(AgentName name){ super("test"); } public FOS processPer(int id)throws MalformedLogicException{ System.out.println("perceiving module"); return FOS.createFOS("testing"); } public FOS processAction(int id, FOS data){ return null; } }
In AFME, factory classes are used to create instances of actuators, perceptors, modules, and gui modules when an agent arrives at its destination. To implement a factory class, the developer must implement the appropriate factory interface for the type of class in question. That is, implementations of ActuatorFactory, PerceptorFactory, ModuleFactory, and GUIFactory must be provided for actuators, perceptors, modules, and gui modules respectively. The name of the factory class must be a combination of the name of the class to be created appended with the string Fact. The class must be in the same package.
It should be noted that all is required to use migration in AFME is the MigrateActuator. The other classes in this example are just included to illustrate the use of factories, to trigger the migration process, and to retract the destination. The developer will typically implement application specific code in triggering the migration process, for actuator, perceptors etc, and for determining the destination. The following is the code for the timing perceptor factory.
package migration; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; import com.agentfactory.cldc.PerceptorFactory; /** Creates an instance of the timing perceptor using the perception manager for * the agent. * * @author Conor Muldoon */ public class TimingPerceptorFact implements PerceptorFactory{ public Perceptor createPerceptor(PerceptionManager manager){ return new TimingPerceptor(manager); } }
The following is the code for the test perceptor factory.
package migration; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; import com.agentfactory.cldc.PerceptorFactory; /** Creates an instance of the TestPerceptor using the specified perception * manager. It should be noted that factory classes are only required when migration is being used. * * @author Conor Muldoon */ public class TestPerceptorFact implements PerceptorFactory{ public Perceptor createPerceptor(PerceptionManager manager){ return new TestPerceptor(manager); } }
The following is the code for the test module factory.
package migration; import com.agentfactory.cldc.AgentName; import com.agentfactory.cldc.Module; import com.agentfactory.cldc.ModuleFactory; public class TestModuleFact implements ModuleFactory{ public Module createModule(AgentName an){ return new TestModule(an); } }
This example does not illustrate the use of actuator or GUI factories. Take a look at the CVS source code for AFME on SourceForge for examples of actuator and GUI factories. They are used in a similar manner to perceptor and module factories.
AFME provides support for weak migration; the agent’s design (commitment rules and roles) or mental state is not present agent the destination, but the Java classes that the agent uses are. The reason for this is that no support for the dynamic loading of foreign is provided in MIDP. Local classes can still be dynamically loaded, but the classes must be pre-verified and packaged within the same Jar file as the MIDlet.
The following is the code for the destination platform.
package migration; platform Africa{ // Number of threads in operation on the platform scheduler 1; service com.agentfactory.cldc.migration.MigrationManager 1007 "socket://navanman.ucd.ie:6060" Africa; template com/agentfactory/cldc/compiler/MIDlet.template Africalet com/agentfactory/cldc/compiler/MigAgentPlatform.template AfricaAgentPlatform; }
Save the code in a file called Africa.af. Again, change the host name and port number passed to the migration service to the appropriate values. Generate the MIDlet and agent platform for the destination and compile the Java code in the usual manner.
To run the application, the migration server must first be started. The migration server operates on a Standard PC and operates outside the firewall domain. To run the migration server, the AFME server Jar file must be added to the classpath. The migration server and been developed using standard Java and must be executed on a Java 5 JVM or higher. Type the following on the command line or in a shell in Linux environments.
java com.agentfactory.migration.server.ServerApp 6060The argument to the ServerApp class specifies the port number on which the server will operate. Change the port number to the appropriate value for the application.
In the previous example, both the source and destination platforms were using the same migration server to send and receive incoming agents. In situations whereby there are a large number of agent platforms in operation a number of migration servers will be in operation. This increases the scalability of the systems and improves fault tolerance in that there is not a single point of failure for the entire agent collective. In situations whereby the destination platform of the agent is using a different migration server than the source platform of the agent, the agent will be forwarded on to the appropriate migration server. To illustrate the use of agent forwarding, in the previous example change the port number passed to the migration service in Africa.af to a different number. In Ireland.af, leave the port number passed to the migration service the same, but change the port number in the destination belief added to Alice to the appropriate number for Africa platform. Regenerate and compile the code for Ireland.af and Africa.af. Subsequently, start two migration servers operating with the port numbers for the Ireland and Africa platforms. When the agent migrates, the migration server will forward on the agent to appropriate platform, where it will be stored until the destination platform checks for incoming agents. The host name for the servers could, of course, also have been altered.
Migration in CDC operates in a similar manner as in CLDC/MIDP. The only difference the developer must make is in the template used to generate the Java source code for the agent platform. The following is the script for a destination CDC agent platform.
package migration; platform AfricaCDC{ // Number of threads in operation on the platform scheduler 1; service com.agentfactory.cldc.migration.MigrationManager 1007 "socket://navanman.ucd.ie:6060" AfricaCDC; template com/agentfactory/cldc/compiler/CDCMigPlatform.template AfricaCDCAgentPlatform; }
Save the script in a file called AfricaCDC.af and regenerate the code. Notice that the only real difference (apart from the name of the platform) to the destination is on the template line. Compile the code using a CDC compiler. When compiling the code for a CDC environment, it may be necessary to place the source for the CDC platform and actuators perceptors etc. in a different folder than the code for a MIDP environment.
To test the application, the MIDP source developed earlier shall be used. If you previously did the agent forwarding example, make sure the port numbers used for the destination belief in the source platform (Ireland.af) and passed to the migration service in the destination platform (AfricaCDC.af) match up. If they do not match up, do not forget to regenerate the code once they have been changed and refresh the folder if you are using an IDE. If you are not running a large number of agent platforms, it is sufficient to use a single migration server. Do not forget to change the name in the destination belief in the source platform (Ireland.af) added to Alice from Africa to AfricaCDC and again regenerate and refresh if this change had not been made.
Run the migration server and use the previously developed MIDP source platform to test the application. When running the CDC destination, the AFME platform Jar file without interface dependencies must be included on the classpath rather than the MIDP AFME platform Jar file (see the section on creating platforms without user interfaces). This illustrates the migration of an agent from a MIDP device, such as a mobile phone, to a CDC device, such as the Stargate.
The AFME peer to peer services enable direct communication and migration between agent platforms. The original message transport and migration services of AFME were designed to enable agents pierce the service provider firewalls of GPRS and 3G networks, thus communication and migration had to be facilitated through an intermediary server operating outside the firewall domain. In situations whereby devices are communicating directly, such as in an ad-hoc Wireless LAN, or are operating in network that does not contain a firewall, it will be more appropriate to use the AFME peer to peer services than the original approach. In such cases, no intermediary server will be required. The remainder of this section illustrates how to use the peer to peer message transport service and the peer to peer migration service. To use the peer to peer functionality of AFME, the peer to peer jar file (p2pX_X.jar) must be on the classpath.
It should be noted that in these examples it is assumed that the platforms are operating on MIDP devices, such as mobile phones. If a platform is to operate on a CDC device, such as the Stargate WSN gateway, the CDCP2PMigrationManager and CDCPeerToPeerMTS should be used rather than the P2PMigrationManager and PeerToPeerMTS. This alteration should be reflected in the platform script. To use these classes, the CDC peer to peer Jar file (p2p_cdcX_X.jar) must be on the classpath. Additionally, when migration is being used, the CDCMigPlatform template should be used rather than the MigAgentPlatform template.
To illustrate the use of the peer to peer message transport service in AFME, we shall alter the previously developed network chatter application so as that it uses the peer to peer message transport service rather than the standard message transport service. The following is the modified platform script for Alice’s platform.
package netchat; gui chatter.ChatterInterface; platform Aliceplat{ // Number of threads in operation on the platform scheduler 2; service com.agentfactory.cldc.p2pmts.PeerToPeerMTS mts Aliceplat unblocked 9998; create Alice chatter.Chatter 991; start Alice; template com/agentfactory/cldc/compiler/MIDlet.template Aliceplatlet com/agentfactory/cldc/compiler/AgentPlatform.template AliceplatAgentPlatform; }
The following is the modified platform script for Bob's platform.
package netchat; gui chatter.ChatterInterface; platform Bobplat{ // Number of threads in operation on the platform scheduler 2; service com.agentfactory.cldc.p2pmts.PeerToPeerMTS mts Bobplat unblocked 9999; create Bob chatter.Chatter 991; add Bob chatUp(agentID(Alice,addresses("socket://navanman.ucd.ie:9998"))); start Bob; template com/agentfactory/cldc/compiler/MIDlet.template Bobplatlet com/agentfactory/cldc/compiler/AgentPlatform.template BobplatAgentPlatform; }
Compile the code for the platforms in the usual manner. When executing the code, start Alice’s platform first. Subsequently, start Bob’s platform. There is no need to run the external message server because it is not used by the peer to peer message transport service.
This example illustrates how to use the AFME peer to peer migration service. To use the peer to peer migration service, the migration Jar file must be on the classpath along with the peer to peer Jar file. In this example, we shall alter the agent platforms of the migration example that uses the standard AFME original migration service. Change the Ireland.af script to the following.
package migration; platform Ireland{ // Number of threads in operation on the platform scheduler 1; service com.agentfactory.cldc.p2pmig.P2PMigrationManager migration; create Alice migration.Sparrow 1000; add Alice always(dest("socket://navanman.ucd.ie:10000")); start Alice; template com/agentfactory/cldc/compiler/MIDlet.template Irelandlet com/agentfactory/cldc/compiler/MigAgentPlatform.template IrelandAgentPlatform; }
Change the script for the destination, Africa.af, to the following.
package migration; platform Africa{ // Number of threads in operation on the platform scheduler 1; service com.agentfactory.cldc.p2pmig.P2PMigrationManager migration 10000; template com/agentfactory/cldc/compiler/MIDlet.template Africalet com/agentfactory/cldc/compiler/MigAgentPlatform.template AfricaAgentPlatform; }
If the port number is not passed to the peer to peer migration manager, a dynamic port number is assigned for the server socket to receive incoming agents. In this example, the Ireland platform has a dynamic assigned port number, whereas the Africa platform has a specified port number of 10000. Dynamic port number assignment is useful in that the developer may not know a priori what port numbers are available on certain devices such as a phone, which may have several applications in operation. The discovery section illustrates how agents can dynamically obtain the IP and port numbers of specific platforms at run time.
No change is required to the Sparrow agent design, so compile and run the application; ensuring that the destination platform is operational before the agent decides to migrate.
When agents communicate and migrate in a peer to peer manner, the discovery of the addresses of agents becomes an issue. This section illustrates how the AFME discovery server is used to provide yellow and white pages services. The AFME discovery server has been designed to operate in a CDC/CLDC environment and, as such, has a relatively small footprint when compared to the standard Agent Factory discovery services. AFME agents can still avail of the standard large scale discovery services of Agent Factory if they wish, by communicating with an agent on a standard platform, but the AFME discovery server is used in situations whereby the entire agent infrastructure must be embedded and perhaps disconnected from the Internet. For example, when multiple mobile and embedded devices connect to a Stargate via an ad-hoc wireless connection, a discovery server could operate on the Stargate. To run the discovery server the AFME Jar file, along with the peer to peer Jar file, must be on the class path since the server has a dependency on the AFME scheduler buffer.
To use the discovery server, the following should be issued from the console.
com.agentfactory.cldc.p2pmts.DiscServer 11000
Alternatively, the main method could be invoked directly from Java code, such as within a MIDlet. In this example, the discover server is operating on port 11000. That is, the first argument to the main method represents the port number. Change the port number to the appropriate value for your application. To test the discovery server, we shall create two platforms. One that registers the platform and local agents with the discovery server, the other that has an agent operating on it that will lookup and obtain the registered information from the discovery server.
The following is the platform script for the platform that will register with the discovery server.
package disctest; platform DiscReg{ // Create 1 thread on the platform scheduler 1; service com.agentfactory.cldc.p2pmts.PeerToPeerMTS mts DiscReg unblocked 9999 "socket://navanman.ucd.ie:11000"; create Bob disctest.RegAgent 1000; add Bob test("socket://navanman.ucd.ie:11000"); start Bob; template com/agentfactory/cldc/compiler/MIDlet.template DiscReglet com/agentfactory/cldc/compiler/AgentPlatform.template DiscRegAgentPlatform; }
Don’t forget to change the host and port numbers to the appropriate values. The peer to peer message transport service will register the name and address of the platform with the server, along with the name(s) of the agent(s) executing on the platform.
The following is the design for Bob, the registration agent.
act com.agentfactory.cldc.p2pmts.RegYP; test(?addr) > registerYP(?addr,Tester,Bob);
Store the design in a file called RegAgent.sh. The registration agent registers itself (Bob) as a Tester agent with the discovery server yellow pages. The following is the script for the platform that will host the agent that will discover the registered information.
package disctest; platform DiscChk{ // Create 1 thread on the platform scheduler 1; create Alice disctest.ChkAgent 1000; add Alice test, discAddr("socket://navanman.ucd.ie:11000"); start Alice; template com/agentfactory/cldc/compiler/MIDlet.template DiscChklet com/agentfactory/cldc/compiler/AgentPlatform.template DiscChkAgentPlatform; }
The following is the agent design for Alice, ChkAgent.sh.
act com.agentfactory.cldc.PrintActuator, com.agentfactory.cldc.p2pmts.LookupPlat, com.agentfactory.cldc.p2pmts.LookupWP, com.agentfactory.cldc.p2pmts.LookupYP; test,discAddr(?discURL)> par(lookupPlat(?discURL,DiscReg), lookupWP(?discURL,Bob), lookupYP(?discURL,Tester)); platformAddr(?platform,?platURL)>print(platformAddr(?platform,?platURL)); whitePages(?agt,?address)>print(whitePages(?agt,?address)); yellowPages(?server,?agt,?addr)>print(yellowPages(?server,?agt,?addr));
When Alice begins operating, the lookup platform, lookup white pages, and lookup yellow pages actuators are triggered. These actuators are used when an agent wishes to obtain the IP address of a platform, wishes to obtain the IP address of an agent, and wishes to obtain the name and IP address of an agent that performs a particular service respectively. Once this information has been obtained, Alice prints it to the console, but it could be used for any purpose. It should be noted that the lookup actuators block agent execution in making socket connections. The register yellow pages actuator does not block. That is, it is executed in a separate thread.
This example will illustrate how to put agents into a torpor or sleep state. The notion of a torpor or sleep state in AFME is different from a thread sleep state (hence use of the term torpor rather than sleep). When an agent is in a torpor state, certain events (a load bang) can wake it up. The agents deliberation process is only performed when the agent is fully awake. When an agent is in a torpor state, however, a check is still performed at the agents response rate to check if any events have occurred since the agent went into a torpor state or the previous check. If an event has occurred, the agent is woken up and its deliberation process is executed. To be able to trigger a wakeup another thread must be in operation that causes an event to occur. The following is a design of a sleep agent.
per SleepTime; act SleepAct,com.agentfactory.cldc.PrintActuator; sleepTime > torpor; alive > print(Awake);
Once the agent adopts the belief sleepTime, it goes into a torpor state. If the agent is awake and has adopted the belief alive, it will print the message Awake to the console. The following is the agent platform script for the example.
package sleeper; platform Sleeper{ // Create 1 thread on the platform scheduler 1; create Bob sleeper.SleeperAgent 1000; add Bob always(alive); start Bob; template com/agentfactory/cldc/compiler/Deploylet.template Sleeperlet com/agentfactory/cldc/compiler/DeployPlatform.template SleeperAgentPlatform; }
Compile the code in the usual manner. The agent design contains a sleep perceptor. This perceptor generates the sleepTime belief once every five iterations of the control algorithm. It also creates a thread that will trigger the wake event after 7 and a half seconds. The wake event is triggered by calling the wake method of the perception manager.
package sleeper; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; import com.agentfactory.cldc.logic.FOS; public class SleepTime extends Perceptor { final PerceptionManager pm; int curTime; /** Creates an instance of SleepTime. * * @param manager the perception manager for the agent. */ public SleepTime(PerceptionManager manager){ super(manager); pm=manager; } public void perceive() { curTime++; if(curTime==5){ curTime=0; adoptBelief("sleepTime"); new Thread(){ public void run(){ try{ sleep(7500); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("waking"); pm.wake(); } }.start(); } } }
The following is the code for the sleep actuator, which is used to put an agent into a torpor state by calling the torpor method of the affect manager.
package sleeper; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; /** This actuator puts an agent into a torpor state. Its trigger is torpor. * */ public class SleepAct extends Actuator { AffectManager am; public SleepAct(AffectManager manager) { super(manager,"torpor"); am=manager; } public boolean act(FOS action) { am.torpor(); return true; } }
It should be noted that other agents can wake a sleeping agent up through the use of a platform service and the wake method of the Scheduler class, which takes the name of the agent to be awoken as an argument.
This example will illustrate how to modify the original HelloWorld application so as that it can operate on a Sun SPOT wireless sensor network node. Rather than printing Hello World to the console, the application is altered so as that it toggles the Sun SPOT LEDs on and off. When developing for Sun SPOTs, make sure that the afme_verifiedX_X.jar jar file is used rather than afmeX_X.jar. It should be noted that this example will not work in the SPOTWorld (Solarium) emulator because the platform template (DeployPlatform.template) has dependencies on the record management store functionality; the way to get it working in the emulator, by altering the platform script, is discussed later in this section. The record management store is available on real Sun SPOTs, but is not, at present, supported in the emulator. The following is the agent design for the HelloAgent.
act HelloAct; sayHello>printHello;
The design is identical to that used in the original HelloWorld example. This illustrates the advantage of not qualifying the HelloAct actuator; the same design can be used when working with different imperative actuators, modules, and perceptors. See the tip in Network Chatter for the disadvantage of not qualifying class names. Follow the procedure described in the HelloWorld example for compiling the agent platform script. The following is the platform script for this example.
package sunspot; platform HelloWorld{ // Create 1 thread on the platform scheduler 1; create Alice sunspot.HelloAgent 1000; add Alice always(sayHello); start Alice; template com/agentfactory/cldc/compiler/Deploylet.template HelloWorldlet com/agentfactory/cldc/compiler/DeployPlatform.template HelloWorldAgentPlatform; }
The main difference in this platform script and the original is that different templates are used for generating the Java code. The deployment templates are used since they do not have dependencies on user interface functionality. Also note that the HelloAgent is qualified by sunspot. This is assuming that the design is saved in the sunspot folder. The original design could, of course, be used without moving it, provided it was on the classpath. In that case, the HelloAgent would be qualified by helloworld.
To compile code for the emulator, you need to change the line:
template com/agentfactory/cldc/compiler/Deploylet.template HelloWorldlet com/agentfactory/cldc/compiler/DeployPlatform.template HelloWorldAgentPlatform;to the following:
template com/agentfactory/cldc/compiler/Deploylet.template HelloWorldlet com/agentfactory/cldc/compiler/EmuPlatform.template HelloWorldAgentPlatform;
That is, the EmuPlatform.template should be used rather than the DeployPlatfrom.template in the agent platform script.
The following is the code for the HelloAct actuator.
package sunspot; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; import com.sun.spot.sensorboard.EDemoBoard; import com.sun.spot.sensorboard.IDemoBoard; import com.sun.spot.sensorboard.peripheral.ITriColorLED; public class HelloAct extends Actuator { boolean b; private IDemoBoard sensorBoard; public HelloAct(AffectManager manager) { super(manager,"printHello"); sensorBoard=EDemoBoard.getInstance(); } public boolean act(FOS action) { ITriColorLED[]leds= sensorBoard.getLEDs(); if(b){ b=false; for(int i=0;i<8;i++){ leds[i].setRGB(200, 0, 0); leds[i].setOn(); } }else{ b=true; for (int i = 0; i < 8; i++){ leds[i].setRGB(0, 0, 0); leds[i].setOff(); // turn off all LEDs } } return true; } }
Once the imperative code for the application has been generated, through the use of the AFME compiler, it is compiled and deployed to the Sun SPOT in the usual manner (ensure that the manifest file of the Jar to be deployed reflects the name of the generated MIDlet).
Note: the binary classes for AFME must be included in the Jar file deployed to the Sun SPOT. The binaries can be added to the Jar file through the use of either a shell script, make file, IDE options, or an ant script in the build process. For example, the user.classpath and utility.jars user properties can be set in the typical Sun SPOT application build.properties file.
This example illustrates the AFME Sun SPOT wireless message transport service. To use the AFME Wireless Sun SPOT communication infrastructure, the AFME radiogram Jar file must be downloaded. In this example, two agents, Alice and Bob, on separate Sun SPOTs will communicate wirelessly. As the agents communicate, they toggle the Sun SPOT LEDs on and off. The agent platforms are packaged in the wireless directory. The following is the agent platform script for Alice's platform:
package wireless; platform WirelessAliceplat{ // Number of threads in operation on the platform scheduler 3; service com.agentfactory.radio.RadiogramMTS WirelessAliceplat 40; //// Bob IEEE radio address: c101.84ac.0000.1002 create Alice wireless.Chatter 991; start Alice; template com/agentfactory/cldc/compiler/Deploylet.template WirelessAliceplatlet com/agentfactory/cldc/compiler/EmuPlatform.template WirelessAliceplatAgentPlatform; }
It should be noted that the second parameter to the radiogram MTS is the port number. The following is the agent design for both agents:
act com.agentfactory.cldc.mts.RequestActuator, NmTog, LEDAct; per com.agentfactory.cldc.mts.MTSPerceptor, com.agentfactory.cldc.SelfPerceptor; chatUp(?agent) > request(?agent,chat); name(?n)>nmT(?n); message(request,sender(?agt,?addr),chat) > par(request(agentID(?agt,?addr),chat),toggleLED);
The following is the code for the actuator to turn on and off the LEDs:
package wireless; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; import com.sun.spot.peripheral.Spot; import com.sun.spot.sensorboard.EDemoBoard; import com.sun.spot.sensorboard.peripheral.ISwitch; import com.sun.spot.sensorboard.peripheral.ITriColorLED; import com.sun.spot.peripheral.radio.IRadioPolicyManager; import com.sun.spot.io.j2me.radiostream.*; import com.sun.spot.io.j2me.radiogram.*; import com.sun.spot.util.*; import com.sun.spot.sensorboard.IDemoBoard; import java.io.*; import javax.microedition.io.*; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; public class LEDAct extends Actuator { boolean b; private IDemoBoard sensorBoard; public LEDAct(AffectManager manager) { super(manager,"toggleLED"); sensorBoard=EDemoBoard.getInstance(); } public boolean act(FOS action) { ITriColorLED[]leds= sensorBoard.getLEDs(); if(b){ b=false; for(int i=0;i<8;i++){ leds[i].setRGB(0, 0, 0); leds[i].setOff(); } }else{ b=true; for (int i = 0; i < 8; i++){ leds[i].setRGB(200, 0, 0); leds[i].setOn(); } } return true; } }
In this example, the NmTog actuator is used to toggle an LED 4 for Bob and LED 0 for Alice. The following is the code for NmTog:
package wireless; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; import com.sun.spot.sensorboard.EDemoBoard; import com.sun.spot.sensorboard.IDemoBoard; import com.sun.spot.sensorboard.peripheral.ITriColorLED; public class NmTog extends Actuator { boolean b; private IDemoBoard sensorBoard; public NmTog(AffectManager manager) { super(manager,"nmT(?n)"); sensorBoard=EDemoBoard.getInstance(); } public boolean act(FOS action) { String n=action.next().toString(); int k=0; if(n.equals("Bob")){ k=4; } ITriColorLED[]leds= sensorBoard.getLEDs(); if(b){ b=false; leds[k].setRGB(0, 0, 0); leds[k].setOff(); }else{ b=true; leds[k].setRGB(200, 0, 0); leds[k].setOn(); } return true; } }
The following is the agent platform script for Bob's agent platform:
package wireless; platform WirelessBobplat{ // Number of threads in operation on the platform scheduler 3; service com.agentfactory.radio.RadiogramMTS WirelessBobplat 40; create Bob wireless.Chatter 991; // Alice IEEE radio address: c101.84ac.0000.1001 // Spot address 0014.4F01.0000.3FA2 add Bob chatUp(agentID(Alice,addresses("radiogram://0014.4F01.0000.3FA2:40"))); start Bob; template com/agentfactory/cldc/compiler/Deploylet.template WirelessBobplatlet com/agentfactory/cldc/compiler/EmuPlatform.template WirelessBobplatAgentPlatform; }
When running the agent platform, Alice's platform should be started before Bob's. In this example, the EmuPlatform template is used to generate code for the Sun SPOT emulator (Solarium or Sun SPOT World). To generate code for a real SPOT, the DeployAgentPlatform should be used.
Note: the classes from radiogram_verX_X.jar must be included in the Jar file deployed to the Sun SPOT (see the note in the section that discusses the hello world Sun SPOT application).
This example illustrates how to use agent migration on the Sun SPOT. To use the Sun SPOT migration functionality, the radio migration (radiomig_verX_X.jar) and the migration_verX_X.jar Jar files must be downloaded from SourceForge. In this example, an agent will move from one Sun SPOT to another, and then migrate back to the source. When an agent is resident on a SPOT it will toggle an LED on and off. In this example, the code will be packed in the directory spotmig. The following is the agent platform script for the source platform (the platform where the agent will begin executing):
package spotmig; platform SpotIreland{ // Number of threads in operation on the platform scheduler 3; service com.agentfactory.radio.RadioMigManager migration 45 52; service LocalServ; create Alice spotmig.SpotSparrow 1000; // Spot address 0014.4F01.0000.3FA2 // Local address C101.84AC.0000.1001 add Alice always(ieeeAddr("\"+ com.sun.spot.peripheral.Spot.getInstance() .getRadioPolicyManager().getIEEEAddress() + \":45")), always(destAddr("0014.4F01.0000.3FA2:46")); start Alice; template com/agentfactory/cldc/compiler/Deploylet.template SpotIrelandlet com/agentfactory/cldc/compiler/EmuMigPlatform.template SpotIrelandAgentPlatform; }
The RadioMigManager is the service for supporting AFME agent migration on Sun SPOTs. The RadioMigManager uses both radiogram and radiostream connections. The first number specified as an argument to the RadioMigManager is the radiogram port number. The second number is the radiostream port number. It should be noted that if the RadiogramMTS is used in an application along with the RadioMigManager, the radiogram port number specified for RadioMigManager must be different than that of the RadiogramMTS. In the above example, the template used to generate the platform is EmuMigPlatform.template. This template is used when the developer wishes to generate code for the Sun SPOT emulator i.e. Solarium or Sun SPOT World. To generate code for a real SPOT, the DeployMigPlatform.template should be used. The following is the agent design for the mobile agent:
act com.agentfactory.cldc.RetractActuator, com.agentfactory.cldc.AdoptActuator, com.agentfactory.cldc.migration.MigrateActuator, com.agentfactory.cldc.PrintActuator, LEDTog; per TimingPerceptor; true>ledTog(3); timeToMigrate, destAddr(?destaddr), ieeeAddr(?addr) > par(migrate(?destaddr,null), retractBelief(always(destAddr(?destaddr))),retractBelief(always(ieeeAddr(?addr))), adoptBelief(always(ieeeAddr(?destaddr))),adoptBelief(always(destAddr(?addr))));
The agent design should be saved in a file called SpotSparrow.sh. When an agent is present on a platform it toggles one of the LEDs. The following is the code for the toggle actuator:
package spotmig; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; import com.sun.spot.sensorboard.EDemoBoard; import com.sun.spot.sensorboard.IDemoBoard; import com.sun.spot.sensorboard.peripheral.ITriColorLED; public class LEDTog extends Actuator { boolean b; private IDemoBoard sensorBoard; public LEDTog(AffectManager manager) { super(manager,"ledTog(?led)"); sensorBoard=EDemoBoard.getInstance(); } public boolean act(FOS action) { int k=Integer.parseInt(action.next().toString()); ITriColorLED[]leds= sensorBoard.getLEDs(); if(b){ b=false; leds[k].setRGB(0, 0, 0); leds[k].setOff(); }else{ b=true; leds[k].setRGB(0, 200, 0); leds[k].setOn(); } return true; } }
With migration in AFME, factory classes must be created for actuators, perceptors, and modules that form part of the agent design. The following is the factory code for the toggle actuator:
package spotmig; import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.ActuatorFactory; import com.agentfactory.cldc.AffectManager; public class LEDTogFact implements ActuatorFactory{ public Actuator createActuator(AffectManager am){ return new LEDTog(am); } }
In this example, both the source and the destination agent platforms have a local service operating that toggles a different LED than the mobile agent. The following is the code for the local platform service:
package spotmig; import com.agentfactory.cldc.AgentName; import com.agentfactory.cldc.Platform; import com.agentfactory.cldc.Service; import com.agentfactory.cldc.logic.FOS; import com.agentfactory.cldc.scheduler.Scheduler; import com.sun.spot.sensorboard.EDemoBoard; import com.sun.spot.sensorboard.IDemoBoard; import com.sun.spot.sensorboard.peripheral.ITriColorLED; public class LocalServ extends Service implements Runnable{ boolean b; private IDemoBoard sensorBoard; public LocalServ(String[] args, Object[] agtNms, Scheduler scheduler,Platform plat) { super("ledserv"); scheduler.schedule(new int[0],this,1000); sensorBoard=EDemoBoard.getInstance(); } public void modifyBinding(Object a,Object b){ } public FOS processAction(AgentName an, int id,FOS fos){ return null; } public FOS processPer(AgentName an,int id){ return null; } public void run(){ ITriColorLED[]leds= sensorBoard.getLEDs(); if(b){ b=false; leds[2].setRGB(0, 0, 0); leds[2].setOff(); }else{ b=true; leds[2].setRGB(100, 100, 0); leds[2].setOn(); } } }
To trigger the migration process, the mobile agent must adopt the belief timeToMigrate. This belief is generated by timing perceptor. The following is the code for the timing perceptor:
package spotmig; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; /** * * @author Conor Muldoon */ public class TimingPerceptor extends Perceptor{ long time; /** Creates a new instance of TimingPerceptor */ public TimingPerceptor(PerceptionManager manager) { super(manager); time=System.currentTimeMillis(); } public void perceive(){ if(System.currentTimeMillis()-time>10000){ adoptBelief("timeToMigrate"); } } }
A factory class is required to create the timing perceptor in the migration process; the following is the code:
package spotmig; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; import com.agentfactory.cldc.PerceptorFactory; public class TimingPerceptorFact implements PerceptorFactory{ public Perceptor createPerceptor(PerceptionManager pm){ return new TimingPerceptor(pm); } }
The following is the agent platform script for the destination platform:
package spotmig; platform SpotAfrica{ // Number of threads in operation on the platform scheduler 3; service com.agentfactory.radio.RadioMigManager migration 46 52; service LocalServ; template com/agentfactory/cldc/compiler/Deploylet.template SpotAfricalet com/agentfactory/cldc/compiler/EmuMigPlatform.template SpotAfricaAgentPlatform; }
For this example to work, the destination and source platforms must be running on different Sun SPOTs. Additionally, the developer must ensure that the correct IEEE address for the destination is specified in the source platform script.
Note: the classes from radiomig_verX_X.jar and migration_verX_X.jar must be included in the Jar file deployed to the Sun SPOT (see the note in the section that discusses the hello world Sun SPOT application).
When migrating to and from the Sun SPOT base station, the same radio migration manager class (RadioMigManager) is used. A MIDlet should not be generated using the AFME compiler, however. That is, com/agentfactory/cldc/compiler/Deploylet Xlet should be removed from the platform script, where X is the name of the platform. The constructor for the agent platform requires a MIDlet class as an argument. Just create an empty class of the required name. In a separate class that contains a main method, create and run the agent platform. The agent platform template has this constructor as it was originally designed for situations in which MIDlets are present. In a future release, this constructor will be removed for this type of situation.
AFME is hosted on SourceForge and all of the source code may be obtained through the use of a CVS client. If you do not wish to use a CVS client, the CVS repository can be browsed directly on the SourceForge site. Different versions of AFME are tagged according to the converion versionX_X, where X_X represents the version number. For instance, Version 2.7 of AFME is tagged as version2_7 and Version 3.0 is tagged as version3_0.
The source code is also available on GitHub.
Note: The AFME compiler cannot be compiled directly from the source code on SourceForge using only a Java compiler. The grammar files for AFME and the short hand AFAPL language must first be compiled using the ANTLR parser generator tool.
In Version 3.0 of AFME, support has been added for the evaluation of rudimentary mathematical expressions by the control algorithm. In the older versions of the system, this would have to be performed imperatively in Java. The following illustrates how to use expressions:
number(?x), #?x>10 > doSomething;
In this example, if the agent adopts the belief number(?x) and the variable ?x is greater than 10, the commitment to doSomething will be adopted. Support is provided for equalities and inequalities using the symbols >, <, >=, <=, and = for greater than, less than, greater than or equal to, less than or equals to, and equal to respectively. Additionally, the addition and subtraction of variables and numbers can be performed. The following illustrates the use of addition:
number(?x), otherNumber(?y), #?x+10=?y > doSomething;
A number of classes have been created to aid the developer in creating agents directly in Java (without using the AFME compiler). In certain situations, it will be quicker to write agents in this manner rather than writing a template for the compiler and generating the code. For instance, in cases where the developer wishes to quickly test a piece of code or integrate agents into a pre-existing application. This comes at a cost, however, in that more parsing is occuring at runtime rather than compile time. Additionally, since commitment rules are written directly in Java strings, there is no syntax checking of the rules, therefore certain errors will not be identified until runtime. It should also be noted that when creating rules in this manner, support is not currently provided for parsing belief labels or mathematical expressions. If the developer wishes to use belief labels or mathematical expressions, they must use the AFME compiler for creating the platform.
To avail of this functionality the developer must have the builder Jar file (builderX_X.jar) on the classpath. The following illustrates how to create a simple agent in Java.
public class Tester0 { public static void main(String[]args){ Builder b=new Builder(); AgentWrapper aw=new AgentWrapper("Bob","MyApp",1,1,0); aw.addRule("random(?num) > print(?num)"); aw.addInitialBelief("someBelief"); AffectManager am=b.createAM(aw); PerceptionManager pm=b.createPM(aw); aw.addPerceptor(new NumberPerceptor(pm)); aw.addActuator(new PrintActuator(am)); b.scheduleAgent(aw, 1000); b.start(); } }
In the above example, the agent was created in a main method. The same agent could easily be created within a MIDlet as follows.
import javax.microedition.midlet.MIDlet; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.PrintActuator; import com.agentfactory.cldc.builder.AgentWrapper; import com.agentfactory.cldc.builder.Builder; public class Testlet extends MIDlet { Builder b; public Testlet(){ b=new Builder(); } public void startApp(){ Builder b=new Builder(); AgentWrapper aw=new AgentWrapper("Bob","MyApp",1,1,0); aw.addRule("random(?num) > print(?num)"); aw.addInitialBelief("someBelief"); AffectManager am=b.createAM(aw); PerceptionManager pm=b.createPM(aw); aw.addPerceptor(new NumberPerceptor(pm)); aw.addActuator(new PrintActuator(am)); b.scheduleAgent(aw, 1000); b.start(); } public void display(){ } public void pauseApp() { b.pause(); } public void destroyApp(boolean unconditional) { b.destroy(); } }
The following is the code for the number perceptor:
import java.util.Random; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; public class NumberPerceptor extends Perceptor{ Random r; public NumberPerceptor(PerceptionManager manager){ super (manager); r=new Random(); } public void perceive(){ int a=r.nextInt(); int b=r.nextInt(); adoptBelief("random("+a+")"); adoptBelief("random("+b+")"); //System.out.println(a); //System.out.println(b); } }
The agent wrapper class contains methods for adding initial beliefs, commitment rules, actuators, perceptors, and modules. The builder class enables services to be added. The role class contains methods for adding role specific commitment rules and triggers. The following illustrates the use of the methods:
import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.AgentName; import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.PrintActuator; import com.agentfactory.cldc.builder.AgentWrapper; import com.agentfactory.cldc.builder.Builder; import com.agentfactory.cldc.builder.Role; public class Tester { public static void main(String[]args){ Builder b=new Builder(); AgentName an=new AgentName("Bob","MyApp"); AgentWrapper aw=new AgentWrapper(an,2,2,0); aw.addRule("random(?num) > print(?num)'10,0"); aw.addRule("data(?d), random(?num) > par(print(?d),testItems)"); Role test=new Role("test"); test.addRule("a,b,c>doSomething"); test.addTrigger("trig1"); test.addTrigger("trig2"); aw.addRole(test); aw.addInitialBelief("trig1"); aw.addInitialBelief("next(a)"); aw.addInitialBelief("next(b)"); aw.addInitialBelief("next(c)"); b.addService(new TestService()); aw.addModule(new TestModule(an)); AffectManager am=b.createAM(aw); PerceptionManager pm=b.createPM(aw); aw.addPerceptor(new NumberPerceptor(pm)); aw.addPerceptor(new TestPer(pm)); aw.addActuator(new TestActuator(am)); aw.addActuator(new PrintActuator(am)); aw.addActuator(new SomethingAct(am)); b.scheduleAgent(aw, 1000); b.start(); } }
Note: In the above example a different constructor was used for the builder because the agent name was required to pass to the constructor of the test module. If the application does not contain modules, the original constructor is used.
The following is the code for the test module:
import com.agentfactory.cldc.AgentName; import com.agentfactory.cldc.Module; import com.agentfactory.cldc.logic.FOS; import com.agentfactory.cldc.logic.MalformedLogicException; public class TestModule extends Module{ public TestModule(AgentName an){ super("testmodule"); } public FOS processPer(int perceptionID)throws MalformedLogicException{ return FOS.createFOS("testModule"); } public FOS processAction(int actionID,FOS data)throws MalformedLogicException{ System.out.println("testing module"); return null; } }
The following is the code for the test service:
import com.agentfactory.cldc.AgentName; import com.agentfactory.cldc.Service; import com.agentfactory.cldc.logic.FOS; import com.agentfactory.cldc.logic.MalformedLogicException; public class TestService extends Service{ public TestService(){ super("testservice"); } public void modifyBinding(Object oldName, Object newName){ } public FOS processPer(AgentName agentName,int perceptionID)throws MalformedLogicException{ return FOS.createFOS("testService"); } public FOS processAction(AgentName agentName,int actionID,FOS data) throws MalformedLogicException{ System.out.println("testing service"); return null; } }
The following is the code for the test actuator:
import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; public class TestActuator extends Actuator{ AffectManager affMan; public TestActuator(AffectManager am){ super(am, "testItems"); affMan=am; } public boolean act(FOS fos){ affMan.actOn("testmodule", 0, null); affMan.actOn("testservice", 0, null); return true; } }
The following is the code for the test perceptor:
import com.agentfactory.cldc.PerceptionManager; import com.agentfactory.cldc.Perceptor; public class TestPer extends Perceptor{ PerceptionManager perMan; public TestPer(PerceptionManager pm){ super(pm); perMan=pm; } public void perceive(){ String s="data("+perMan.perManage("testservice", 0).toString(); s+='_'+perMan.perManage("testmodule", 0).toString()+')'; adoptBelief(s); } }
The following is the code for the something actuator:
import com.agentfactory.cldc.Actuator; import com.agentfactory.cldc.AffectManager; import com.agentfactory.cldc.logic.FOS; public class SomethingAct extends Actuator{ public SomethingAct(AffectManager am){ super(am,"doSomething"); } public boolean act(FOS action){ System.out.println("doing something"); return true; } }
In AFME templates are used to generate Java code from agent design and platform files. The AFME compiler reads the content of the template and applies information from the agent design and platform files to it.
When writing a template, the developer specifies the Java code that the compiler will generate. It should be noted that the code does not necessarily have to be Java code but all examples presented here use Java. The compiler has not been tested for other languages, but in principle there is no reason why it should not work.
When writing a template, the developer delimits the code using the
agents{ $=Agent =$ agtname $=;=$ }
The above schema would produce the following if there were two agents specified in the agent platform file called Bob and Alice.
Agent Bob; Agent Alice;
Once the compiler encounters the agents keyword, it reads the delimited Java code and then for each agent specified in the agent platform file, it prints out the code and concatenates it with various attributes of each agent, such as the agent's name. Various attributes are available for each type of object contained in the sets. Other top level sets include designs, services, agents, and starters. Within the top level sets, there are lower level sets. For instance, within the designs set, each design will contain a set of rules. The AFME compiler expands the abstract representation provided by templates for each element of a set.
The delimited Java code does not have to be specified within a set construct. For example, it is possible to specify Java code at the start of a file or code specific to a singleton attribute of the platform, such as the name of the platform. The following illustrates how different classes can be created using the name attribute of the platform.
$=public class =$ name $={ }=$
This represents the most trivial Java template that could be created apart from an empty file. It would produce the following if the name of the platform was TestPlatform.
public class TestPlatform{ }
The same template could be used to create a different Java file if used with a different agent platform file.
In the examples discussed in the programmers' guide, there are only a small number of agents in operation. When deploying a large number of agents on a platform, there are a couple of things to keep in mind. For instance, the number of threads in the agent platform should be increased. In order to do this, change the number specified using the scheduler keyword in the agent platform script. Another thing to note is that the response times of the agents will also most likely need to be changed. The response time is the last number specified when creating an agent in the platform script. There is no hard and fast rule in determining the best values for the number of threads in operation or the responsiveness of the agents. Typically, it’s a question of trial and error to determine what works best or what meets the requirements of the application in question. If there are a large number of scheduled tasks in the application, such as IO tasks, the number of threads should be increased.