3. Comunicación

3.1 Fundamentos

3.2 Envío/recepción de mensajes

3.3 Selección de mensajes

3.4 Páginas amarillas (DF Agent)

3.5 Páginas blancas (AMS Agent)

3.6 Protocolos de comunicación

3.6.1 El paquete jade.proto

3.6.1.1 Manejadores (handle y registerHandler)
3.6.1.2 Preparadores (prepare y registerPrepare)
3.6.1.3 El almacen de datos (DataStore)

3.6.2 AchieveRE

3.6.3 ContractNet

3.6.4 Propose

3.6.5 Subscription

3.7 Ejercicios

3.8 Ejercicios (segunda parte)


arriba.png

3.1 Fundamentos

  • La comunicación entre agentes es fundamental para poder conseguir la potencia propia de los sistemas multiagente
  • Determina el comportamiento social de los agentes
  • Para que los agentes se puedan comunicar deben usar el mismo lenguaje de comunicación
  • Un lenguaje de comunicación define los tipos de mensaje: informar, solicitar, preguntar…
  • Las conversaciones entre agentes se rigen por una serie de protocolos de interacción
  • El lenguaje de comunicación de agentes (ACL) permitirá transmitir una serie de conocimiento que vendrá expresado en un lenguaje de contenido. Los términos del lenguaje de contenido que representen conocimiento pertenecerán a un vocabulario común a los distintos agentes que se llama ontología.


En el caso de JADE el lenguaje de comunicación entre agentes es FIPA-ACL.
Un mensaje FIPA-ACL puede contener los siguientes campos:
  • performative: tipo de acto comunicativo (acción que realiza el mensaje). Es el único campo obligatorio y puede tomar uno de los siguientes valores:
    • accept-proposal: aceptar una propuesta recibida previamente
    • agree: estar de acuerdo en realizar alguna acción
    • cancel: cancelar alguna acción pedida previamente
    • cfp: solicitar propuestas para realizar una acción dada
    • confirm: informar a un receptor que una proposición es cierta
    • disconfirm: informar a un receptor que una proposición es falsa
    • failure: informar a otro agente que se intentó una acción pero falló
    • inform: informar a un receptor que una proposición es cierta
    • inform-if: si el agente que recibe la acción cree que la sentencia es verdadera informará de manera afirmativa, sino indicará que es falsa.
    • inform-ref: permite que el emisor informe al receptor de un objeto que cree que corresponde a un descriptor, como puede ser un nombre u otra descripción que lo identifique.
    • not-understood: informar a un receptor que el emisor no entendió el mensaje
    • propagate: el receptor trata el mensaje como si fuese dirigido directamente a él, y debe identificar los agentes en este descriptor y enviarles el mensaje a ellos
    • propose: enviar una propuesta para realizar una cierta acción
    • proxy: el receptor debe seleccionar agentes objetivo denotados por una descripción dada, y enviarles un mensaje embebido
    • query-if: preguntarle a otro agente si una determinada proposición es cierta
    • query-ref: preguntar a otro agente por el objeto referenciado en una expresión
    • refuse: rechazar realizar una acción
    • reject-proposal: rechazar una propuesta durante una negociación
    • request: solicitar a un receptor que realice alguna acción
    • request-when: solicitar al receptor que realice alguna acción cuando una proposición dada sea cierta
    • request-whenever: solicitar al receptor que realice alguna acción cada vez que una proposición dada sea cierta
    • subscribe: una intención persistente de notificar al emisor de un determinado valor, y volver a notificarle cada vez que dicho valor cambie
  • sender: AID del emisor
  • receiver: lista de AID’s de los receptores
  • reply-to: receptor de un mensaje reply
  • content: contenido del mensaje
  • language: lenguaje en que se expresa el contenido
  • encoding: codificación del contenido
  • ontology: ontología usada para dar significado a los términos del contenido
  • protocol: identificador del protocolo de interacción
  • conversation-id: identificador de la conversación. Esto es especialmente útil cuando un agente mantiene varias conversaciones a la vez.
  • reply-with: indica una expresión que tendrá que ser usada por el agente que responda a dicho mensaje. Si un agente envía un mensaje que contiene :reply-with query1 el receptor responderá con otro que contenga :in-reply-to query1
  • in-reply-to: Hace referencia a que este mensaje es una respuesta a otro anterior.
  • reply-by: Indica el tiempo en que el mensaje ha de ser respondido.


arriba.png

3.2 Envío/recepción de mensajes

  • El intercambio de mensajes entre agentes en JADE se realiza mediante mensajes FIPA-ACL
  • Mecanismo: paso asíncrono de mensajes
  • Cada agente tiene una cola de mensajes entrantes
  • La lectura efectiva de los mensajes es a voluntad del agente
  • Un agente puede:
    • Leer el primer mensaje en la cola
    • Leer el primer mensaje que satisfaga un requisito
  • La cola de mensajes es única para cada agente y, por lo tanto, es compartida por todos los comportamientos
  • Cada vez que se coloca un mensaje en la cola el agente receptor es avisado
  • Un comportamiento puede ser bloqueado en espera de la recepción de un mensaje: sincronización
  • Los mensajes intercambiados por agentes son instancias de la clase jade.lang.acl.ACLMessage


Comuncacion_1.JPG
Comunicación 1


Métodos de ACLMessage

A continuación se muestran algunos de los métodos más importantes de la clase ACLMessage. Para consultar más detenidamente todos los métodos de dicha clase se puede consultar su documentación en la API de JADE ( ACLMessage )

  • setPerformative(int ): toma como parámetro una constante representativa de un tipo de acción performativa y la establece como performativa del mensaje. Los posibles actos comunicativos son los que hemos visto antes y las constantes que los representan se pueden ver en la API. Por ejemplo, para hacer que el mensaje msg sea de tipo agree bastará con escribir: msg.setPerformative(ACLMessage.AGREE);
  • getPerformative(): devuelve un entero equivalente a la constante que representa a la performativa del mensaje
  • createReply(): crea un mensaje de respuesta para el mensaje sobre el que es aplicado, poniendo los valores oportunos en campos como receiver, conversation-id, etc.
  • addReceiver(AID ): toma como parámetro un AID y lo añade a la lista de receptores
  • getAllReceiver(): devuelve un iterador sobre la lista de receptores.
  • setContent(String ): recibe como parámetro una cadena y la pone como contenido del mensaje
  • getContent(): devuelve una cadena con el contenido del mensaje

El resto de métodos get/set son similares, simplemente varía el tipo del valor introducido o devuelto. Por ejemplo, getSender() devolverá un AID mientras que getLanguage() devolverá una cadena.

La performativa puede indicarse directamente pasando el valor al constructor de ACLMessage, lo cuál es recomendable porque todos los mensajes deben contener al menos la performativa.


Enviar un mensaje

- Pasos:
  • Crear un objeto ACLMessage
  • Usar los métodos de ACLMessage para rellenar los campos necesarios
  • Llamar al método send() de la clase Agent. El método send() recibe como parámetro un ACLMessage, añade el valor oportuno al campo sender (remitente) y envía el mensaje a los destinatarios

- Ejemplo:
ACLMessage mensaje = new ACLMessage(ACLMessage.INFORM);
mensaje.setLanguage("Español");
mensaje.addReceiver(id);
mensaje.setContent("Hola Receptor");
send(mensaje);
 

- Mensaje enviado:
(INFORM
 :sender  ( agent-identifier :name emisor@luna:1099/JADE  :addresses (sequence http://luna:7778/acc ))
 :receiver  (set ( agent-identifier :name receptor@luna:1099/JADE ) )
 :content  "Hola Receptor"
 :language  Español
)


Recibir un mensaje

Se usará el método receive() de la clase Agent. El método receive() obtiene el primer mensaje de la cola de mensajes y lo devuelve (devuelve null si la cola está vacía). El mesaje recibido es exactamente igual al anterior.
ACLMessage mensaje = receive();

Ejemplo de intercambio de mensajes entre dos agentes:

Esta clase representa al agente Emisor, que es el que se encarga de emitir el mensaje "Hola que tal receptor?".

package examples.practica4;
 
import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.*;
 
public class Emisor extends Agent
{
    class EmisorComportaminento extends SimpleBehaviour
    {
        boolean fin = false;
        public void action()
        {
            System.out.println(getLocalName() +": Preparandose para enviar un mensaje a receptor");
            AID id = new AID();
            id.setLocalName("receptor");
 
        // Creación del objeto ACLMessage
            ACLMessage mensaje = new ACLMessage(ACLMessage.REQUEST);
 
        //Rellenar los campos necesarios del mensaje
            mensaje.setSender(getAID());
            mensaje.setLanguage("Español");
            mensaje.addReceiver(id);
            mensaje.setContent("Hola, que tal receptor ?");
 
        //Envia el mensaje a los destinatarios
            send(mensaje);
 
            System.out.println(getLocalName() +": Enviando hola a receptor");
            System.out.println(mensaje.toString());
            fin = true;
        }
 
        public boolean done()
        {
            return fin;
        }
    }
    protected void setup()
    {
        addBehaviour(new EmisorComportaminento());
    }
}


Y esta otra representa al agente Receptor que recibe el mensaje del agente Emisor.
package examples.practica4;
 
import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.ACLMessage;
 
public class Receptor extends Agent
{
    class ReceptorComportaminento extends SimpleBehaviour
    {
            private boolean fin = false;
            public void action()
            {
                System.out.println(" Preparandose para recibir");
 
            //Obtiene el primer mensaje de la cola de mensajes
                ACLMessage mensaje = receive();
 
                if (mensaje!= null)
                {
                    System.out.println(getLocalName() + ": acaba de recibir el siguiente mensaje: ");
                    System.out.println(mensaje.toString());
                    fin = true;
                }
            }
            public boolean done()
            {
                return fin;
            }
    }
    protected void setup()
    {
        addBehaviour(new ReceptorComportaminento());
    }
}

- En la plataforma JADE lanzamos:
  1. Un agente de la clase Receptor llamándolo con el nombre “receptor” (java jade.Boot -container receptor:examples.practica4.Receptor).
  2. Un agente de la clase Emisor llamándolo con el nombre "emisor1" (java jade.Boot -container emisor1:examples.practica4.Emisor).

- Obtenemos la siguiente salida:
emisor1: Preparandose para enviar un mensaje a receptor
emisor1: Enviando hola a receptor
receptor: acaba de recibir el siguiente mensaje:
(REQUEST
 :sender  ( agent-identifier :name emisor1@luna:1099/JADE  :addresses (sequence http://luna:7778/acc ))
 :receiver  (set ( agent-identifier :name receptor@luna:1099/JADE ) )
 :content  "Hola, que tal receptor ?"
 :language  Español )

Nota importante: Si hubiésemos lanzado primero el emisor no se habría obtenido ninguna salida porque al no estar registrado el receptor del mensaje en el AMS, este enviaría un mensaje FAILURE al agente emisor.


Problema: si el Receptor no recibe ningún mensaje, éste está ejecutándose continuamente, consumiendo CPU.

Posibles soluciones:
  • Solución: Usar el método block() de la clase Behaviour:
public void action()
{
    ACLMessage mensaje = receive();
    if (mensaje != null)
    {
        ...
    }else
    {
        System.out.println("Receptor: Esperando a recibir mensaje...");
        block();
    }
}
Por lo tanto la clase Receptor quedaría así:
package examples.practica4;
 
import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.ACLMessage;
 
public class Receptor extends Agent
{
    class ReceptorComportaminento extends SimpleBehaviour
    {
            private boolean fin = false;
            public void action()
            {
                System.out.println(" Preparandose para recibir");
 
            //Obtiene el primer mensaje de la cola de mensajes
                ACLMessage mensaje = receive();
 
                if (mensaje!= null)
                {
                    System.out.println(getLocalName() + ": acaba de recibir el siguiente mensaje: ");
                    System.out.println(mensaje.toString());
                    fin = true;
                }else{
                    System.out.println("Receptor: Esperando a recibir mensaje...");
                    block();
                }
            }
            public boolean done()
            {
                return fin;
            }
    }
    protected void setup()
    {
        addBehaviour(new ReceptorComportaminento());
    }
}

  • Solución: Usar el método blockingReceive() de Agent en lugar del método receive():
public void action()
{
    ACLMessage mensaje = blockingReceive();
    if (mensaje != null)
    {
        ...
    }
}

Por lo tanto la clase Receptor quedaría así:
package examples.practica4;
 
import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.ACLMessage;
 
public class Receptor extends Agent
{
    class ReceptorComportaminento extends SimpleBehaviour
    {
            private boolean fin = false;
            public void action()
            {
                System.out.println(" Preparandose para recibir");
 
            //Obtiene el primer mensaje de la cola de mensajes
                ACLMessage mensaje = blockingReceive();
                if (mensaje!= null)
                {
                    System.out.println(getLocalName() + ": acaba de recibir el siguiente mensaje: ");
                    System.out.println(mensaje.toString());
                    fin = true;
                }
            }
            public boolean done()
            {
                return fin;
            }
    }
    protected void setup()
    {
        addBehaviour(new ReceptorComportaminento());
    }
}


Diferencia entre las dos soluciones:
  • block(): solo bloquea un comportamiento del agente hasta que llegue un mensaje, el bloqueo no se produce hasta que termina el comportamiento sobre el que se aplica.
  • blockingReceive(): bloquea instantáneamente todos los comportamientos del agente hasta que llegue un mensaje, además devuelve el mensaje recibido que liberó el bloqueo.


Responder a mensajes:
Se puede hacer de dos formas:
  • Indicando como receptor al emisor del mensaje anterior. De esta manera, la clase Emisor quedaría así:
package examples.practica4;
 
import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.*;
 
public class Emisor extends Agent
{
    class EmisorComportaminento extends SimpleBehaviour
    {
        boolean fin = false;
        public void action()
        {
            System.out.println(getLocalName() +": Preparandose para enviar un mensaje a receptor");
            AID id = new AID();
            id.setLocalName("receptor");
 
        // Creación del objeto ACLMessage
            ACLMessage mensaje = new ACLMessage(ACLMessage.REQUEST);
 
        //Rellenar los campos necesarios del mensaje
            mensaje.setSender(getAID());
            mensaje.setLanguage("Español");
            mensaje.addReceiver(id);
            mensaje.setContent("Hola, que tal receptor ?");
 
       //Envia el mensaje a los destinatarios
            send(mensaje);
            System.out.println(getLocalName() +": Enviando hola a receptor");
            System.out.println(mensaje.toString());
 
       //Espera la respuesta
            ACLMessage mensaje2 = blockingReceive();
            if (mensaje2!= null)
            {
                System.out.println(getLocalName() + ": acaba de recibir el siguiente mensaje: ");
                System.out.println(mensaje2.toString());
                fin = true;
            }
        }
 
        public boolean done()
        {
            return fin;
        }
    }
    protected void setup()
    {
        addBehaviour(new EmisorComportaminento());
    }
}

Y el de la clase Receptor asi:
package examples.practica4;
 
import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.ACLMessage;
 
public class Receptor extends Agent
{
    class ReceptorComportaminento extends SimpleBehaviour
    {
            private boolean fin = false;
            public void action()
            {
                System.out.println(" Preparandose para recibir");
 
            //Obtiene el primer mensaje de la cola de mensajes
                ACLMessage mensaje = receive();
                if (mensaje!= null)
                {
                    System.out.println(getLocalName() + ": acaba de recibir el siguiente mensaje: ");
                    System.out.println(mensaje.toString());
 
                // Envia constestación
                    System.out.println(getLocalName() +": Enviando contestacion");
                    ACLMessage respuesta = new ACLMessage( ACLMessage.INFORM );
                    respuesta.setContent( "Bien" );
                    respuesta.addReceiver( mensaje.getSender() );
                    send(respuesta);
                    System.out.println(getLocalName() +": Enviando Bien a receptor");
                    System.out.println(respuesta.toString());
                    fin = true;
                }
            }
            public boolean done()
            {
                return fin;
            }
    }
    protected void setup()
    {
        addBehaviour(new ReceptorComportaminento());
    }
}

  • Haciendo uso de createReply().
De esta manera, la clase Receptor quedaría así:
package examples.practica4;
 
import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.ACLMessage;
 
public class Receptor extends Agent
{
    class ReceptorComportaminento extends SimpleBehaviour
    {
            private boolean fin = false;
            public void action()
            {
                System.out.println(" Preparandose para recibir");
 
            //Obtiene el primer mensaje de la cola de mensajes
                ACLMessage mensaje = receive();
                if (mensaje!= null)
                {
                    System.out.println(getLocalName() + ": acaba de recibir el siguiente mensaje: ");
                    System.out.println(mensaje.toString());
 
                // Envia constestación
                    System.out.println(getLocalName() +": Enviando contestacion");
                    ACLMessage respuesta = mensaje.createReply();
                    respuesta.setPerformative( ACLMessage.INFORM );
                    respuesta.setContent( "Bien" );
                    send(respuesta);
                    System.out.println(getLocalName() +": Enviando Bien a receptor");
                    System.out.println(respuesta.toString());
                    fin = true;
               }
            }
            public boolean done()
            {
                return fin;
            }
    }
    protected void setup()
    {
        addBehaviour(new ReceptorComportaminento());
    }
}

Y el de la clase Emisor no varía con respecto al anterior ejemplo.

arriba.png

3.3 Selección de mensajes


Para seleccionar los mensajes que un agente desea recibir podemos utilizar la clase MessageTemplate.
Esta clase permite definir filtros para cada atributo del mensaje ACLMessage y se utiliza como parámetro en los métodos receive y blockingReceive.

En esta clase se definen un conjunto de métodos estáticos que nos devuelven como resultado un objeto de tipo MessageTemplate.

Las diferentes opciones de filtrado son:
  • MatchPerformative( performative ) donde performativepuede ser:
    • ACLMessage.INFORM
    • ACLMessage.PROPOSE
    • ACLMessage.QUERY_REF
    • ...
  • MatchSender( AID )
  • MatchConversationID( String ): Permite dedicar un comportamiento a gestionar una conversación/negociación.
  • and( Template1, Template2 ): realiza un and lógico entre los dos filtros.
  • or ( Template1, Template2 ): realiza un or lógico entre los dos filtros.
  • not( Template ): invierte el filtro.
  • MatchOntology( String ): devuelve un filtro según el nombre de la ontologia que le pasemos como parámetro.
  • MatchProtocol( String ): devuelve un filtro según el nombre del protocolo que le pasamos.
  • MatchLanguage( String ): devuelve un filtro según el lenguaje introducido.
  • MatchContent( String ): crea una plantilla según el contenido del mensaje.
  • MatchReplyWith( String ): devuelve un filtro según el campo replywith.

Además existe el método match(ACLMessage) que devuelve verdadero si el mensaje que le pasamos como parámetro cumple el filtro definido en el objeto MessageTemplate.

En el ejemplo anterior, el agente Receptor podría solo estar interesado en recibir mensajes del tipo REQUEST, en Español y procedentes exclusivamente del agente "emisor". Esto se puede obtener modificando el comportamiento:
package examples.practica4;
 
import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
 
public class FiltroReceptor extends Agent
{
      class Comportamiento extends SimpleBehaviour
      {
          boolean fin = false;
          MessageTemplate plantilla = null;
          public Comportamiento ()
          {
            AID emisor = new AID();
            emisor.setLocalName("emisor");
 
          //Devuelve una plantilla de mensaje que coincida con algún mensaje con un slot :sender dado.
            MessageTemplate filtroEmisor = MessageTemplate.MatchSender(emisor);
 
          //Devuelve una plantilla de mensaje que coincida con algún mensaje con una perfomativa dada.
            MessageTemplate filtroInform = MessageTemplate.MatchPerformative(ACLMessage.REQUEST);
 
          //Devuelve una plantilla de mensaje que conicida con algún mensaje con una slot :language dado
            MessageTemplate filtroIdioma = MessageTemplate.MatchLanguage("Español");
 
          //Operación lógica AND entre dos objetos de esta clase.
            plantilla = MessageTemplate.and(filtroInform,filtroEmisor);
            plantilla = MessageTemplate.and(plantilla,filtroIdioma);
          }
 
          public void action()
          {
            ACLMessage mensaje = receive(plantilla);
 
            if (mensaje!= null)
            {
                System.out.println(getLocalName() + ": ha recibido el siguiente mensaje: ");
                System.out.println(mensaje.toString());
                fin = true;
            }else
            {
                System.out.println(getLocalName() + ":Esperando mensajes...");
                block();
            }
        }
 
        public boolean done()
        {
            return fin;
        }
    }
 
    protected void setup()
    {
        addBehaviour(new Comportamiento());
    }
}
 



arriba.png

3.4 Páginas amarillas (DF Agent)


Permite a los agentes publicar los servicios que proporcionan, para que otros agentes puedan acceder a ellos.

paginasAmarillas.png

Los agentes interactúan con el DF intercambiando mensajes ACL usando el lenguaje de SL0 y la ontología FIPA-agent-management. Jade nos facilita esta tarea mediante los métodos implementados en la clase DFService .

Publicar servicios:
  • El agente debe proporcionar al DF una descripción, incluyendo su AID, los protocolos, lenguajes y ontologías que el resto de agentes necesitan conocer para interactuar con él; y la lista de servicios publicados.
  • Para cada servicio se proporciona una descripción, incluyendo: tipo de servicio, nombre, protocolos, lenguajes y ontologías; y una serie de propiedades específicas del servicio.
  • Antes de finalizar su ejecución el agente debe eliminar del DF sus servicios.


Para realizar las acciones anteriores Jade proporciona los siguientes métodos:
  • static DFAgentDescription register: registra los servicios de un agente en el DF.
  • static void deregister: elimina del registro del DF los servicios del agente.
  • Los servicios se definen con los siguientes métodos de la clase ServiceDescription:
    • void setName: modifica el nombre del servicio.
    • void setOwnership: modifica el propietario del servicio.
    • void setType: modifica el tipo de servicio.
    • void addLanguages: añade lenguaje del servicio.
    • void addOntologies: añade ontología del servicio.
    • void addProtocols: añade protocolo del servicio.
    • void addProperties: añade propiedades del servicio.
  • La descripcion del agente se modifica con los siguientes métodos de la clase DFAgentDescription:
    • void setName: modifica el AID de la descripciñón del agente.
    • void addServices: añade el servicio pasado por parámetro a la descripción del agente.
    • void removeServices: elimina el servicio pasado por parámetro a la descripción del agente.
    • void addLanguages: añade lenguajes que el agente entiende.
    • void addOntologies: añade ontologías que el agente entiende.
    • void addProtocols: añade protocolos que el agente entiende.


En el siguiente ejemplo, habrá dos agentes que ofrecen servicio y uno que los solicita.
Esta es la clase Ofrece que representa a uno de los agentes que ofrece servicios:
package examples.practica4;
 
import jade.core.Agent;
import jade.core.AID;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.*;
import jade.domain.FIPAException;
 
public class Ofrece extends Agent
{
    protected void setup()
    {
    // Descripción del agente
        DFAgentDescription descripcion = new DFAgentDescription();
        descripcion.setName(getAID());
        descripcion.addLanguages("Castellano");
 
    // Descripcion de un servicio se proporciona
        ServiceDescription servicio = new ServiceDescription();
        servicio.setType("Tipo del servicio 1");
        servicio.setName("Nombre del servicio 1");
 
    // Añade dicho servicio a la lista de servicios de la descripción del agente
        descripcion.addServices(servicio);
 
    // Descripcion de otro servicio se proporciona
        servicio = new ServiceDescription();
        servicio.setType("Tipo del servicio 2");
        servicio.setName("Nombre del servicio 2");
 
    // Añade dicho servicio a la lista de servicios de la descripción del agente
        descripcion.addServices(servicio);
 
        try
        {
            DFService.register(this, descripcion);
        }
        catch (FIPAException e)
        {
            e.printStackTrace();
        }
    }
 
    protected void takeDown()
    {
        try
        {
            DFService.deregister(this);
        }
        catch (FIPAException fe)
        {
            fe.printStackTrace();
        }
        System.out.println("El agente "+getAID().getName()+" ya no ofrece sus servicios.");
    }
}

Esta es la clase Ofrece2 que representa al segundo agente que ofrece servicios:
package examples.practica4;
 
import jade.core.Agent;
import jade.core.AID;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.*;
import jade.domain.FIPAException;
 
public class Ofrece2 extends Agent
{
    protected void setup()
    {
    // Descripción del agente
        DFAgentDescription descripcion = new DFAgentDescription();
        descripcion.setName(getAID());
        descripcion.addLanguages("Castellano");
 
    // Descripcion de un servicio se proporciona
        ServiceDescription servicio = new ServiceDescription();
        servicio.setType("Tipo del servicio 3");
        servicio.setName("Nombre del servicio 3");
 
    // Añade dicho servicio a la lista de servicios de la descripción del agente
        descripcion.addServices(servicio);
 
        try
        {
            DFService.register(this, descripcion);
        }
        catch (FIPAException e)
        {
            e.printStackTrace();
        }
    }
 
    protected void takeDown()
    {
        try
        {
            DFService.deregister(this);
        }
        catch (FIPAException fe)
        {
            fe.printStackTrace();
        }
        System.out.println("El agente "+getAID().getName()+" ya no ofrece sus servicios.");
    }
}

Esta es la clase Pide que representa al agente que busca en las páginas amarillas agentes que ofrezcan los servicios que solicita:
package examples.practica4;
 
import jade.core.Agent;
import jade.core.AID;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.*;
import java.util.Iterator;
 
public class Pide extends Agent
{
    protected void setup()
    {
        ServiceDescription servicio = new ServiceDescription();
        servicio.setType("Tipo del servicio 1");
 
    // Plantilla de descripción que busca el agente
        DFAgentDescription descripcion = new DFAgentDescription();
        descripcion.addLanguages("Castellano");
 
    // Servicio que busca el agente
        descripcion.addServices(servicio);
        try
        {
        // Todas las descripciones que encajan con la plantilla proporcionada en el DF
            DFAgentDescription[] resultados = DFService.search(this,descripcion);
 
            if (resultados.length == 0)
                System.out.println("Ningun agente ofrece el servicio deseado");
 
            for (int i = 0; i < resultados.length; ++i)
            {
                System.out.println("El agente "+resultados[i].getName()+" ofrece los siguientes servicios:");
                Iterator servicios = resultados[i].getAllServices();
                int j = 1;
                while(servicios.hasNext())
                {
                    servicio = (ServiceDescription)servicios.next();
                    System.out.println(j+"- "+servicio.getName());
                    System.out.println();
                    j++;
                }
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

Busqueda de servicios:
  • Un agente que busca servicios debe proporcionar una plantilla de descripción de la clase DFAgentDescription.
  • El resultado de la búsqueda es la lista de todas las descripciones que encajan con la plantilla proporcionada (los campos especificados en la plantilla están presentes en la descripción con los mismos valores, los campos no cubiertos no se comprueban).

Para realizar las acciones anteriores jade proporciona diversos métodos de búsqueda que se encuentran en la clase DFService. En el ejemplo anterior se ha utilizado el método search(Agente, Descripción).


arriba.png

3.5 Páginas blancas (AMS Agent)


• Garantiza que cada agente en la plataforma tenga un único nombre.
• Encargado de proporcionar los servicios de páginas blancas y ciclo de vida, y de mantener el directorio de los identificadores de agentes (AID: Agent Identifier) y su estado.
• Cada agente debe registrarse con el AMS para obtener un AID válido, esta operación en JADE la realizan los agentes de manera automática en el agente AMS por defecto.

Para acceder a los servicios del agente AMS hay que importar la clase AMSService. Esta clase contiene los siguientes métodos:
  • static void register: registra al agente en el AMS. Tanto esta operación como la operación deregister se realizan automáticamente en JADE cuando se ejecutan los métodos setup() y takeDown() respectivamente, por lo tanto no suelen ser usados normalmente.
  • static void deregister: elimina el registro del agente en el AMS.
  • static void modify: modifica los datos del agente en el AMS.
  • static AMSAgentDescription[] search: devuelve la descripción de los agentes registrados en el agente AMS.


En el siguiente ejemplo un agente imprime la información sobre los agentes del sistema accediendo al agente AMS.

package examples.practica4;
 
import jade.core.Agent;
import jade.core.AID;
import jade.domain.AMSService;
import jade.domain.FIPAAgentManagement.*;
 
public class PaginasBlancas extends Agent
{
    protected void setup()
    {
        AMSAgentDescription [] agentes = null;
        System.out.println("El agente " + getAID().getName() + " se ha iniciado.");
 
        try
        {
            SearchConstraints restricciones = new SearchConstraints();
            restricciones.setMaxResults ( new Long(-1) ); /// Todos
            agentes = AMSService.search( this, new AMSAgentDescription (), restricciones );
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
 
        AID miId = getAID();
 
        System.out.println();
        System.out.println("Lista de agentes activos:");
        for (int i=0; i < agentes.length; i++)
        {
            AID id = agentes[i].getName();
            System.out.println(( id.equals( miId ) ? "--> " : "    " ) + "Agente" + i + ": " + id.getName() );
        }
    }
}

arriba.png

3.6 Protocolos de comunicación


Los agentes se pueden comunicar entre sí de varias formas distintas, es decir, las conversaciones que pueden llevar a cabo responden a algún patrón de los especificados por FIPA, denominados protocolos. Cada uno de estos protocolos establece el intercambio básico de mensajes que existe entre dos agentes para un tipo de conversación dada, ya sea una petición, una consulta etc. Ejemplos de estos protocolos son el FIPA-Request, FIPA-Propose, FIPA-Query…Pues bien, en estos protocolos de comunicación JADE define dos roles, el que inicia la conversación y el que es objeto de la misma (rol Initiator y rol Responder). Para la mayoría de protocolos JADE proporciona unas clases de comportamiento prediseñadas para ambos roles. Estas clases se encuentran en el paquete jade.proto.


arriba.png

3.6.1 El paquete jade.proto


Este paquete agrupa todas las clases que, en forma de comportamientos, facilitan la implementación de los protocolos de comunicación definidos por FIPA.
Dichas clases se agrupan dentro del paquete en cuatro parejas de clases principales con las cuales se podrán implementar la mayoría de los protocolos. El motivo de que sean parejas es que para cada protocolo se necesita la clase Initiator y la clase Responder, que implementarán respectivamente el agente que inicie la conversación y el que sea destinatario del primer mensaje, que puede ser un solo agente o varios.
A mayores de estas cuatro clases existen otras subclases que añaden valores a las principales o que las simplifican. Cada par de clases esta indicado para implementar una serie de protocolos:

Comportamientos
Protocolos FIPA
  • AchieveREInitiator
  • AchieveREResponder
    • SimpleAchieveREInitiator
    • SimpleAchieveREResponder
    • IteratedAchieveREInitiator
    • SSIteratedAchieveREResponder
FIPA-Request
FIPA-Query
FIPA-Recruiting
FIPA-Request-When
FIPA-Brokering
  • ContractNetInitiator
  • ContractNetResponder
    • SSContractNetResponder
FIPA-Contract-Net
  • SubscriptionInitiator
  • SubscriptionResponder
FIPA-Subscribe
FIPA-Request-Whenever
  • ProposeInitiator
  • ProposeResponder
FIPA-Propose

Como ya se indicó más arriba estas clases implementan comportamientos, y concretamente el tipo de comportamiento que implementa es el FSMBehaviour, ya que las acciones de los agentes en los protocolos FIPA se puede reducir simplemente a una máquina de estados finitos, y este es el tipo de comportamiento indicado para representar dichas máquinas. Este comportamiento es más fácil de comprender observando el siguiente ejemplo en el cual Pedro le pide la hora Juan:
  1. Pedro: Juan, ¿Tienes hora?
  2. Juan: Sí, un segundo.
  3. Juan mira el reloj.
  4. Juan: Son las seis en punto.

Esta conversación sigue el modelo del protocolo FIPA-Request, es decir, hay una petición del agente Initiator y una aceptación y respuesta del agente Responder. También podrían darse dos casos más a mayores: que Juan no tenga hora, o que la tenga y no se la quiera dar a Pedro. Y la máquina de estados finita que reflejaría esta conversación sería la siguiente:

Conversación.png
Diagrama de estados de los interlocutores de una conversación.

En este ejemplo los rectángulos son representan estados y las flechas eventos. En JADE los comportamientos de los iniciadores se ejecutarán una sola vez, mientras que los de los respondedores serán cíclicos, por lo que volverán al estado inicial tras alcanzar el estado final.
La implementación de las clases del paquete jade.proto está hecha de tal modo que el programador se podrá abstraer de tareas tales como el envío de mensajes o el cambio de estado. La principal tarea del programador será definir qué se debe hacer en cada estado del protocolo y preparar los mensajes antes de ser enviados. La acciones que se realizan en cada estado se definen mediante los manejadores (Handlers), mientras que los mensajes se concretan mediante los preparadores (Prepares). La siguiente figura muestra dicho funcionamiento con claridad:

esquemaRequest2.jpg
Protocolo FIPA-Request asociado a sus manejadores y preparadores.


3.6.1.1 Manejadores (handle y registerHandler)

Un manejador es un comportamiento que se iniciará al entrar en el estado al que esté asociado. Existen dos formas de implementar un manejador, que explicaremos a continuación, con un código de ejemplo equivalente, que imprimirá el mensaje “Inform recibido” cuando se cambie de estado por la llegada de un mensaje Inform:

1. Sobrecarga de métodos handle: Cada comportamiento tiene una serie de métodos handle con la forma: handleInform, handleRequest, etc. Cada uno de ellos será invocado cuando se reciba el tipo de mensaje correspondiente.


protected void handleInform(ACLMessage inform) {
   System.out.println(“Inform recibido”);
}

2. Registrar manejadores propios: Se podrán registrar comportamientos para que actúen como manejadores. El registro se hará a través de los métodos registerHandler que tienen la forma: registerHandleInform, registerHandleRequest, etc. En caso de registrar un comportamiento como manejador, el sistema ignorará las sobrecargas de los métodos handler y se limitará a iniciar los comportamientos registrados.
protected void setup() {
   ...
   this.registerHandleInform(new OneShotBehaviour() {
      public void action() {
         System.out.println(“Inform recibido”);
      }
   });
   ...
}
Cada vez que se entre en un estado nuevo se lanzará un nuevo comportamiento que, en el caso de haber registrado uno nuevo (registerHandle) se lanzará ese mismo, pero si no, se lanzará un OneShotBehaviour que invocará al método handle por defecto de la clase.

Manejadores asociados a preformativas: Hay ciertos manejadores que serán iniciados cada vez que llegue un mensaje con determinada preformativa, pero siempre que ese mensaje llegue según lo esperado por el protocolo FIPA que corresponda.

Manejadores AllResponses: Serán lanzados cuando se reciban todas las respuestas al primer mensaje enviado por el iniciador o cuando se supere el tiempo de espera, indicado en el campo reply-by de ese mensaje inicial. Proporciona acceso a todos los mensajes recibidos. Se encuentra en todos los iniciadores.

Manejadores AllResultNotifications: Serán lanzados cuando se reciban todas las respuestas finales o cuando se supere el tiempo de espera, indicado en el campo reply-by de ese mensaje inicial. Proporciona acceso a todos los mensajes recibidos. Solamente se encuentra en el iniciador AchieveRE y en el ContractNet.

Manejador OutOfSequence: Este es el manejador que se encarga de actuar en el caso de que el mensaje recibido no se corresponda con el esperado según los protocolo FIPA. Es decir, funcionará con las excepciones.


3.6.1.2 Preparadores (prepare y registerPrepare)

Un preparador lo que hace es preparar un mensaje para ser enviado, por lo que son iniciados antes de enviar un mensaje del tipo al que estén asociados.
El trabajo con los preparadores es idéntico al caso de los manejadores, pudiendo también sobrecargar los métodos prepare o registrando comportamientos que implementen los preparadores.
Los métodos prepare devuelven el mensaje o mensajes que se van a enviar. En el caso de registrar comportamientos habrá que guardar en el almacén de datos y con una clave determinada el mensaje que se desea enviar.
Se deber tener mucho cuidado a la hora de rellenar los campos de los mensajes, pues el hecho de dejar vacío un campo puede interrumpir todo el protocolo. Por ello es muy recomendable hacer uso del método createReply() presente en los mensajes ACLMessage. Este método genera un nuevo mensaje de respuesta al original con los campos necesarios cubiertos, así tan sólo habrá que modificar los que correspondan.
Actualmente, gran parte de los preparadores están obsoletos, quedando su responsabilidad en manos de los manejadores.


3.6.1.3 El almacén de datos (DataStore)

El almacén de datos guarda todos los mensajes que se envían o reciben durante todo el proceso de comunicación, es decir, a lo largo de la ejecución de un protocolo. Cada clase define un conjunto de constantes que servirán de clave para acceder a un tipo de mensajes determinado. Estas constantes se caracterizan por emplear el sufijo “KEY” en su nombre. Por ejemplo, si en la clase ProposeResponser se desea registrar el mensaje de respuesta (Response) que se desea enviar, se hará de la siguiente manera:
ACLMessage response = new ACLMessage(ACLMessage.ACCEPT_PROPOSAL);
this.getDataStore().put(ProposeResponder.RESPONSE_KEY, response);
Si lo que se desea es obtener el mensaje Propose recibido, habrá que hacer lo siguiente:
ACLMessage propose = (ACLMessage)
this.getDataStore().get(ACLMessage.PROPOSE_KEY);
Hay que ser cuidadoso al utilizar el almacén de datos, ya que no funciona igual con todas las clases, por lo que sería conveniente mirar la API de cada clase.


3.6.2 AchieveRE

La característica principal de los mensajes en FIPA-ACL es que cada uno de ellos representa un acto comunicativo, es decir, una acción que un agente puede ejecutar. El estándar FIPA especifica para cada acto comunicativo:
  • Precondiciones de Viabilidad (Feasibility Preconditions): Condiciones que necesariamente se deben satisfacer antes de que un agente pueda ejecutar una acción, es decir, antes de que envíe un mensaje.
  • Efecto Razonable (Rational Effect): Efecto esperado como consecuencia de la acción, en otras palabras, la razón por la que ha sido enviado el mensaje.

En el cuadro de más arriba se indicaba qué protocolos implementa el conjunto de clases AchieveRE. Para ello utiliza las clases AchieveREInitiator y AchieveREResponder, una para el rol iniciador y otra para el respondedor. También están las subclases SimpleAchieveREInitiator y SimpleAchieveREResponder, cuyas principales diferencias son que sólo permite conversaciones 1:1 y que no permite al programador registrar comportamientos específicos cómo manejadores de los estados de los protocolos. Otro tipo de subclases son IteratedAchieveREInitiator y SSIteratedAchieveREResponder que implementan una versión iterativa del FIPA-Request, en la que habiendo recibido todas las notificaciones de resultados de los respondedores, el iniciador puede enviar de nuevo mensajes de iniciación.

La sesión de cada protocolo con un respondedor dado terminará si:
  • El iniciador envía al respondedor un mensaje explicito CANCEL en lugar del siguiente mensaje de iniciación.
  • El respondedor responde negativamente con REFUSE, NOT_UNDERSTOOD o FAILURE.
  • El respondedor incluye una bandera de terminación a la notificación de resultado INFORM. Esta bandera de terminación puede detectarse usando el método isSessionTerminated().


Algunos de los protocolos que implementa la clase AchieveRE son:

FIPA-Request
Este protocolo permite a un agente solicitar a otro la realización de una acción y está identificado en el parámetro del protocolo del mensaje con el valor fipa-request. Los mensajes que se intercambian son:
  1. Request, la petición.
  2. Agree, si acepta la petición o refuse si la rechaza.
  3. En caso de que el mensaje anterior fuera un agree: Failure si ocurrió algún error en el proceso, inform-done si se realizó correctamente e inform-result si además se tiene que comunicar el resultado.
A continuación se presenta un ejemplo que hace uso de estas clases.
En este ejemplo un agente (representado por la clase Testigo) avisará a los hospitales pasados como parámetro de que ha habido un accidente de tráfico y pide ayuda. Es decir podremos crear varios hospitales y pasarlos todos como parámetros al testigo.
Los hospitales (representados por la clase Hospital) podrán atender la emergencia o descartarla por estar fuera de su radio de actuación.
Para que funcione bien, hay que lanzar primero el agente Hospital (uno o varios) y luego pasarle como parámetro al agente Testigo uno o mas agentes (en caso de no pasar ninguno dará un error de falta de parámetros).

Testigo
package examples.practica5;
 
import jade.core.Agent;
import jade.core.AID;
import jade.lang.acl.ACLMessage;
import jade.proto.AchieveREInitiator;
import jade.domain.FIPANames;
 
 
public class Testigo extends Agent {
 
    protected void setup()
    {
        Object[] args = getArguments();
        if (args != null && args.length > 0) {
            System.out.println("He visto un accidente! Solicitando ayuda a varios hospitales...");
            ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
            for (int i = 0; i < args.length; ++i)
                msg.addReceiver(new AID((String) args[i], AID.ISLOCALNAME));
            msg.setProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST);
            msg.setContent("accidente a 5 kms");
 
            addBehaviour(new ManejadorInitiator(this,msg));
 
        }
        else System.out.println("Debe de esribir el nombre de un hospital como minimo (pasado como parametro).");
    }
 
    class ManejadorInitiator extends AchieveREInitiator
    {
        public ManejadorInitiator(Agent a,ACLMessage msg) {
            super(a,msg);
        }
 
        protected void handleAgree(ACLMessage agree)
        {
            System.out.println("Hospital " + agree.getSender().getName()
                    + " informa que han salido a atender el accidente.");
        }
 
        protected void handleRefuse(ACLMessage refuse)
        {
            System.out.println("Hospital " + refuse.getSender().getName()
                    + " responde que el accidente esta fuera de su radio de accion. No llegaremos a tiempo!!!");
        }
 
        protected void handleNotUnderstood(ACLMessage notUnderstood)
        {
            System.out.println("Hospital " + notUnderstood.getSender().getName()
                    + " no puede entender el mensaje.");
        }
 
        protected void handleInform(ACLMessage inform)
        {
            System.out.println("Hospital " + inform.getSender().getName()
                    + " informa que han atendido el accidente.");
        }
 
        protected void handleFailure(ACLMessage fallo)
        {
            if (fallo.getSender().equals(myAgent.getAMS())) {
                System.out.println("Alguna de los hospitales no existe");
            }
            else
            {
                System.out.println("Fallo en el hospital " + fallo.getSender().getName()
                        + ": " + fallo.getContent().substring(1, fallo.getContent().length()-1));
            }
        }
    }
 
}

Hospital
package examples.practica5;
 
import java.util.StringTokenizer;
import jade.core.Agent;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.proto.AchieveREResponder;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.NotUnderstoodException;
import jade.domain.FIPAAgentManagement.RefuseException;
import jade.domain.FIPAAgentManagement.FailureException;
 
 
public class Hospital extends Agent {
 
    public double DISTANCIA_MAX;
 
    protected void setup()
    {
        DISTANCIA_MAX=(Math.random()*10);
        System.out.println("Hospital "+getLocalName()+": Esperando avisos...");
        MessageTemplate protocolo = MessageTemplate.MatchProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST);
        MessageTemplate performativa = MessageTemplate.MatchPerformative(ACLMessage.REQUEST);
        MessageTemplate plantilla = MessageTemplate.and(protocolo, performativa);
 
        addBehaviour(new ManejadorResponder(this, plantilla));
    }
 
    class ManejadorResponder extends AchieveREResponder
    {
        public ManejadorResponder(Agent a,MessageTemplate mt) {
            super(a,mt);
        }
 
        protected ACLMessage handleRequest(ACLMessage request)throws NotUnderstoodException, RefuseException
        {
            System.out.println("Hospital "+getLocalName()+": Hemos recibido una llamada de " + request.getSender().getName() + " diciendo que ha visto un accidente.");
            StringTokenizer st=new StringTokenizer(request.getContent());
            String contenido=st.nextToken();
            if(contenido.equalsIgnoreCase("accidente"))
            {
                st.nextToken();
                int distancia=Integer.parseInt(st.nextToken());
                if (distancia<DISTANCIA_MAX)
                {
                    System.out.println("Hospital "+getLocalName()+": Vamos echando chispas!!!");
                    ACLMessage agree = request.createReply();
                    agree.setPerformative(ACLMessage.AGREE);
                    return agree;
                }
                else
                {
                    System.out.println("Hospital "+getLocalName()+": Accidente fuera de nuestro radio de accion. No llegaremos a tiempo!!!");
                    throw new RefuseException("Accidente demasiado lejos");
                }
            }
            else throw new NotUnderstoodException("Hospital manda un mensaje que no puedo entender.");
        }
 
        protected ACLMessage prepareResultNotification(ACLMessage request,ACLMessage response) throws FailureException
        {
            if (Math.random() > 0.2) {
                System.out.println("Hospital "+getLocalName()+": Han vuelto de atender el accidente.");
                ACLMessage inform = request.createReply();
                inform.setPerformative(ACLMessage.INFORM);
                return inform;
            }
            else
            {
                System.out.println("Hospital "+getLocalName()+": Han hecho todo lo posible, lo sentimos.");
                throw new FailureException("Han hecho todo lo posible");
            }
        }
    }
}

FIPA-Query
Este protocolo permite a un agente solicitar a otro un objeto o una comprobación que devuelva un valor de verdad, y en función de que tipo de petición sea será un Query-if (comprobación de verdad) o un Query-ref (cuando se pregunta por algún objeto). Los mensajes serían los siguientes:
  1. Query-If o Query-Ref, la solicitud.
  2. Agree si se acepta o refuse si se rechaza.
  3. Si se aceptó: Failure si ocurrió algún error en el proceso, Inform-T/F con la respuesta si la consulta era un Query-If o Inform-Result si la consulta fue un Query-Ref.
A continuación se presenta un ejemplo que hace uso de estas clases.
En este ejemplo un agente (representado por la clase Viajante) solicita al aeropuerto (representado por la clase Aeropuerto) que compruebe si tiene una reserva registrada.
Para que funcione bien, hay que lanzar primero el agente Aeropuerto y ha de llamarse Lavacolla. Para que la petición del viajante se acepte, el nombre del mismo debe ser mayor que dos caracteres.

Viajante
package examples.practica5;
 
import jade.core.AID;
import jade.core.Agent;
import jade.domain.FIPANames;
import jade.lang.acl.ACLMessage;
import jade.proto.AchieveREInitiator;
 
public class Viajante extends Agent {
 
    protected void setup() {
 
        //Creamos el mensaje de la consulta.
 
        ACLMessage mensaje = new ACLMessage(ACLMessage.QUERY_IF);
        mensaje.setProtocol(FIPANames.InteractionProtocol.FIPA_QUERY);
        mensaje.setContent("¿Tengo la reserva?");
 
        AID id = new AID();
        id.setLocalName("Lavacolla");
        mensaje.addReceiver(id);
 
        //Añadimos el comportamiento de la consulta.
 
        this.addBehaviour(new ComprobarInitiator(this, mensaje));
    }
 
    class ComprobarInitiator extends AchieveREInitiator {
        public ComprobarInitiator(Agent agente, ACLMessage mensaje) {
            super(agente, mensaje);
        }
 
        protected void handleAgree(ACLMessage agree) {
            System.out.printf("Espere un momento por favor, estamos buscando en la Base de Datos.\n", agree.getSender().getLocalName());
        }
 
        protected void handleRefuse(ACLMessage refuse) {
            System.out.printf("%s: En estos momentos todas las operadoras estan ocupadas. No podemos atenderle.\n", this.myAgent.getLocalName(), refuse.getSender().getLocalName());
        }
 
        protected void handleNotUnderstood(ACLMessage notUnderstood) {
            System.out.printf("%s: La operadora no entiende el mensaje.\n", this.myAgent.getLocalName(), notUnderstood.getSender().getLocalName());
        }
 
    protected void handleInform(ACLMessage inform) {
            System.out.printf("La operadora informa: %s.\n", inform.getContent());
        }
 
        protected void handleFailure(ACLMessage fallo) {
            System.out.println(this.myAgent.getLocalName() + ": Se ha producido un fallo.");
        }
    }
}

Aeropuerto
package examples.practica5;
 
import jade.core.Agent;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.FailureException;
import jade.domain.FIPAAgentManagement.NotUnderstoodException;
import jade.domain.FIPAAgentManagement.RefuseException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.proto.AchieveREResponder;
 
public class Aeropuerto extends Agent {
 
    protected void setup() {
        System.out.println(this.getLocalName() + ": Abriendo centralita...");
 
        // Filtrado para recibir sólo mensajes del protocolo FIPA-Query.
 
        MessageTemplate plantilla = AchieveREResponder.createMessageTemplate(FIPANames.InteractionProtocol.FIPA_QUERY);
 
        this.addBehaviour(new ComprobarResponder(this, plantilla));
    }
 
    class ComprobarResponder extends AchieveREResponder {
        public ComprobarResponder(Agent agente, MessageTemplate plantilla) {
            super(agente, plantilla);
        }
 
        protected ACLMessage handleRequest(ACLMessage request)
                throws NotUnderstoodException, RefuseException {
            System.out.printf("Operadora: Hemos recibido una llamada de %s solicitando informacion sobre su reserva.\n", request.getSender().getLocalName());
 
            // Si el solicitante es válido se acepta su petición.
 
            if (comprobarSolicitante(request.getSender().getLocalName())) {
                System.out.println("Operadora: Espere un momento por favor...");
                ACLMessage agree = request.createReply();
                agree.setPerformative(ACLMessage.AGREE);
                return agree;
            } else {
                System.out.println(this.myAgent.getLocalName() + ": Todas las operadoras estan ocupadas.");
                throw new RefuseException("Por favor intentelo de nuevo mas tarde");
            }
        }
 
        protected ACLMessage prepareResultNotification(ACLMessage request, ACLMessage response) throws FailureException {
            ACLMessage inform = request.createReply();
            inform.setPerformative(ACLMessage.INFORM);
            String retorno = "No dispone de ninguna reserva";
 
            if (comprobarSolicitante(request.getSender().getName()))
                retorno = "Si que ha hecho alguna reserva";
 
            inform.setContent(retorno);
            return inform;
        }
 
        // Método simple de aceptación o rechazo de solicitudes.
        private boolean comprobarSolicitante(String nombre) {
            return (nombre.length() > 2);
        }
    }
}
Algunos otros de los protocolos de interes que estan implementados en la clase AchieveRE son:

FIPA-Request-When que permite a un agente solicitar que otro lleve a cabo una acción dada cuando se cumple cierta precondición.

FIPA-Brokering cuya función es dar apoyo en interacciones de negociación.


3.6.3 ContractNet

Las clases ContractNet implementan el comportamiento del protocolo del mismo nombre, cuyo funcionamiento consiste en que el iniciador envía una propuesta a varios respondedores, evalúa las respuestas de éstos, y selecciona la que más le guste (puede no aceptar ninguna). Los mensajes que se intercambian son:

  1. CFP (Call For Proposal), la solicitud de proposición, especificando la acción a llevar a cabo y, si procede, las condiciones sobre la ejecución.
  2. Los respondedores pueden enviar un Refuse para rechazar la solicitud, un Not-Understood si hubo algún fallo en la comunicación o un Propose con la propuesta que le hacen al iniciador.
  3. El iniciador evalúa todas las propuestas recibidas y envía Reject-Proposal para rechazarlas o Accept-Proposal para aceptarlas.
  4. Los respondedores cuya propuesta fue aceptada envían un Failure si algo salió mal, un Inform-Done si se realizó la acción con éxito o un Inform-Result con los resultados de la acción si procede.

El agente iniciador (ContractNetInitiator) posee dos métodos importantes, el handlePropose que es llamado cada vez que se recibe una respuesta y el handleAllResponses que se llama cuando se recibieron todas las respuestas o el tiempo de espera se supera. Y el agente respondedor posee los métopdos handleAcceptProposal y handleRejectProposal, que son llamados en función de si se aceptó su propuesta o no, y cuya principal característica es que ambos reciben como parámetros todos los mensajes que se intercambiaron hasta el momento ambos agentes.

A continuación se presenta un ejemplo que hace uso de estas clases.
En este ejemplo un agente cliente (representado por la clase Cliente) pedirá precios para comprar un coche a varios agentes de venta de coches (representados por la clase Autos). Las ventas de coches podrán hacer una oferta o ninguna y el cliente se quedará con la mejor oferta que no supere el precio máximo establecido por él mismo (deberá de pasarse como parámetro).
Para que funcione bien hay que lanzar primero uno o mas autos para que se registren en el servicio de paginas amarillas, y a continuación el agente Cliente.

Cliente
package examples.practica5;
 
import jade.core.AID;
import jade.core.Agent;
import jade.domain.DFService;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.proto.ContractNetInitiator;
 
import java.util.Date;
import java.util.Vector;
 
public class Cliente extends Agent {
 
    //Número de ofertas que se considerarán.
    private int numeroDeOfertas;
 
    //Precio máximo que se pagará por un coche.
    private int precionMaximo;
 
    protected void setup() {
        //El precio máximo se recibirá como argumento de entrada.
        Object[] args = this.getArguments();
 
        if (args != null && args.length == 1) {
            this.precionMaximo = Integer.parseInt(((String) args[0]));
 
            //Búsqueda del servicio de venta de coches en las páginas amarillas.
            ServiceDescription servicio = new ServiceDescription();
            servicio.setType("Autos");
            servicio.setName("Venta de coches");
 
            DFAgentDescription descripcion = new DFAgentDescription();
            descripcion.addLanguages("Español");
            descripcion.addServices(servicio);
 
            try {
                DFAgentDescription[] resultados = DFService.search(this, descripcion);
                if (resultados.length <= 0) {
                    System.out.println("No existen ventas de coches.");
                } else {
                    System.out.println("Busco coche, hay " + resultados.length + " ofertas...");
 
                    //Creamos el mensaje CFP(Call For Proposal) cumplimentando sus parámetros
                    ACLMessage mensajeCFP = new ACLMessage(ACLMessage.CFP);
                    for (DFAgentDescription agente:resultados) {
                        mensajeCFP.addReceiver(agente.getName());
                    }
            //Protocolo que vamos a utilizar
                    mensajeCFP.setProtocol(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET);
                    mensajeCFP.setContent("Busco coche, ¿proponeis precios?");
 
                    //Indicamos el tiempo que esperaremos por las ofertas.
                    mensajeCFP.setReplyByDate(new Date(System.currentTimeMillis() + 15000));
 
                    //Se añade el comportamiento que manejará las ofertas.
                    this.addBehaviour(new ManejoOpciones(this, mensajeCFP));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
 
        } else {
            System.out.println("Debe de introducir el precio maximo que esta dispuesto a pagar (pasado como parametro)");
        }
 
    } // Fin del setup
 
    private class ManejoOpciones extends ContractNetInitiator {
 
        public ManejoOpciones(Agent agente, ACLMessage plantilla) {
            super(agente, plantilla);
        }
 
        //Manejador de proposiciones.
        protected void handlePropose(ACLMessage propuesta, Vector aceptadas) {
            System.out.printf("%s: Recibida oferta de autos %s. Ofrece un coche por %s euros.\n",
                this.myAgent.getLocalName(), propuesta.getSender().getLocalName(), propuesta.getContent());
        }
 
        //Manejador de rechazos de proposiciones.
        protected void handleRefuse(ACLMessage rechazo) {
            System.out.printf("%s: Autos %s no tiene coches que ofrecer.\n",
                this.myAgent.getLocalName(), rechazo.getSender().getLocalName());
        }
 
        //Manejador de respuestas de fallo.
        protected void handleFailure(ACLMessage fallo) {
            if (fallo.getSender().equals(myAgent.getAMS())) {
 
        //Esta notificacion viene del entorno de ejecución JADE (no existe el receptor)
                System.out.println("AMS: Esta venta de autos no existe o no es accesible");
            } else {
                System.out.printf("%s: Autos %s ha sufrido un fallo.\n",
                    this.myAgent.getLocalName(), fallo.getSender().getLocalName());
            }
            //Falló, por lo tanto, no recibiremos respuesta desde ese agente
            Cliente.this.numeroDeOfertas--;
        }
 
        //Método colectivo llamado tras finalizar el tiempo de espera o recibir todas las propuestas.
        protected void handleAllResponses(Vector respuestas, Vector aceptados) {
 
        //Se comprueba si una venta de autos se pasó del plazo de envío de ofertas.
            if (respuestas.size() < numeroDeOfertas) {
                System.out.printf("%s: %d ventas de autos llegan tarde.\n",
                    this.myAgent.getLocalName(), Cliente.this.numeroDeOfertas - respuestas.size());
            }
 
            //Escogemos la mejor oferta
            int mejorOferta = Integer.MAX_VALUE;
            AID mejorAutos = null;
            ACLMessage aceptado = null;
            for (Object resp:respuestas) {
                ACLMessage mensaje = (ACLMessage) resp;
                if (mensaje.getPerformative() == ACLMessage.PROPOSE) {
                    ACLMessage respuesta = mensaje.createReply();
                    respuesta.setPerformative(ACLMessage.REJECT_PROPOSAL);
                    aceptados.add(respuesta);
 
                    //Si la oferta es la mejor (inferior a todas las otras)
                    //Se almacena su precio y el AID de la venta de autos que la hizo.
                    int oferta = Integer.parseInt(mensaje.getContent());
                    if (oferta <= precionMaximo && oferta <= mejorOferta) {
                        mejorOferta = oferta;
                        mejorAutos = mensaje.getSender();
                        aceptado = respuesta;
                    }
                }
            }
 
            //Si hay una oferta aceptada se modifica su performativa.
            if (aceptado != null) {
                System.out.printf("%s: Decidido!!! Compro el coche de Autos %s\n",
                    this.myAgent.getLocalName(), mejorAutos.getLocalName());
                aceptado.setPerformative(ACLMessage.ACCEPT_PROPOSAL);
            }
        }
 
        //Manejador de los mensajes inform.
        protected void handleInform(ACLMessage inform) {
            System.out.printf("%s: Autos %s te ha enviado el contrato.\n",
                this.myAgent.getLocalName(), inform.getSender().getName());
        }
    }
}

Autos
package examples.practica5;
 
import jade.core.Agent;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.FailureException;
import jade.domain.FIPAAgentManagement.NotUnderstoodException;
import jade.domain.FIPAAgentManagement.RefuseException;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.proto.ContractNetResponder;
 
public class Autos extends Agent {
 
    protected void setup() {
        System.out.printf("Autos %s: A la espera de clientes...\n", this.getLocalName());
 
        //Registro del servicio de venta de coches en las páginas amarillas.
        ServiceDescription servicio = new ServiceDescription();
        servicio.setType("Autos");
        servicio.setName("Venta de coches");
 
        DFAgentDescription descripcion = new DFAgentDescription();
        descripcion.setName(getAID());
        descripcion.addLanguages("Español");
        descripcion.addServices(servicio);
 
        try {
            DFService.register(this, descripcion);
        } catch (FIPAException e) {
            e.printStackTrace();
        }
 
        //Se crea una plantilla que filtre los mensajes a recibir.
        MessageTemplate template = ContractNetResponder.createMessageTemplate(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET);
 
        //Añadimos los comportamientos ante mensajes recibidos
        this.addBehaviour(new CrearOferta(this, template));
    }
 
    //Hacemos una simulación para que pueda dar que existe o no coche (sobre un 80% probab).
    private boolean existeCoche() {
        return (Math.random() * 100 > 20);
    }
 
    //Calculamos un precio para el coche aleatoriamente (estará entre 8000 y 30000).
    private int obtenerPrecio() {
        return (int) (Math.random() * 22000) + 8000;
    }
 
    // Simula fallos en el cálculo de precios.
    private boolean devolverPrecio() {
        return (int) (Math.random() * 10) > 1;
    }
 
    private class CrearOferta extends ContractNetResponder {
        public CrearOferta(Agent agente, MessageTemplate plantilla) {
            super(agente, plantilla);
        }
 
        protected ACLMessage prepareResponse(ACLMessage cfp) throws NotUnderstoodException, RefuseException {
            System.out.printf("Autos %s: Peticion de oferta recibida de %s.\n", this.myAgent.getLocalName(), cfp.getSender().getLocalName());
 
            //Comprobamos si existen ofertas disponibles
            if (Autos.this.existeCoche()) {
                //Proporcionamos la información necesaria
                int precio = Autos.this.obtenerPrecio();
                System.out.printf("Autos %s: Preparando oferta (%d euros).\n", this.myAgent.getLocalName(), precio);
 
                //Se crea el mensaje
                ACLMessage oferta = cfp.createReply();
                oferta.setPerformative(ACLMessage.PROPOSE);
                oferta.setContent(String.valueOf(precio));
                return oferta;
            } else {
                //Si no hay ofertas disponibles rechazamos el propose
                System.out.printf("Autos %s: No tenemos ofertas disponibles.\n", this.myAgent.getLocalName());
                throw new RefuseException("Fallo en la evaluación.");
            }
        }
 
        protected ACLMessage prepareResultNotification(ACLMessage cfp, ACLMessage propose, ACLMessage accept) throws FailureException {
            //Hemos recibido una aceptación de nuestra oferta, enviamos el albarán
            System.out.printf("Autos %s: Hay una posible oferta.\n", this.myAgent.getLocalName());
 
            if (devolverPrecio()) {
                System.out.printf("Autos %s: Enviando contrato de compra.\n", this.myAgent.getLocalName());
 
                ACLMessage inform = accept.createReply();
                inform.setPerformative(ACLMessage.INFORM);
                return inform;
            } else {
                System.out.printf("Autos %s: Vaya!, ha fallado al enviar el contrato.\n", this.myAgent.getLocalName());
                throw new FailureException("Error al enviar contrato.");
            }
        }
 
        protected void handleRejectProposal(ACLMessage cfp, ACLMessage propose, ACLMessage reject) {
            //Nuestra oferta por el coche ha sido rechazada
            System.out.printf("Autos %s: Oferta rechazada por su excesivo precio.\n", this.myAgent.getLocalName());
        }
    }
}


3.6.4 Propose


El protocolo FIPA-Propose implementa un comportamiento que consiste en que el iniciador le pide permiso al respondedor para realizar una acción, y si éste acepta la realiza y si no, no. Los mensajes intercambiados en la ejecución de este protocolo son:
  1. Propose, con la propuesta de acción por parte del iniciador.
  2. Un Reject-Proposal o un Accept-Proposal en función de si acepta la propuesta o no.

Una peculiaridad de las clases Propose es que la del respondedor (ProposeResponder) no dispone de métodos handle, solamente dispone de un prepareResponse() que maneja la respuesta al Propose recibido, un registerPrepareResponse(), un createMessageTemplate() y algunos métodos reset().

A continuación se presenta un ejemplo que hace uso de estas clases.
En este ejemplo el agente que Inicia (representado por la clase ProposeIni) le pedirá permiso al que Responde (representados por la clase ProposeRes) para salir de clase. Sólo si el Respondedor acepta, podrá salir de clase.
Para que funcione correctamente hay que crear el agente de nombre Puerta de tipo ProposeRes (que hay que lanzar primero) y un agente ProposeIni que llamaremos como queramos.

ProposeIni

package examples.practica5;
 
import jade.core.AID;
import jade.core.Agent;
import jade.domain.FIPANames;
import jade.lang.acl.ACLMessage;
import jade.proto.ProposeInitiator;
 
public class ProposeIni extends Agent {
 
    protected void setup() {
        //Se crea un mensaje PROPOSE. Se quiere pedir permiso para salir de clase.
        ACLMessage mensaje = new ACLMessage(ACLMessage.PROPOSE);
        mensaje.setProtocol(FIPANames.InteractionProtocol.FIPA_PROPOSE);
        mensaje.setContent("Puedo salir de clase?");
 
        //Se añade el destinatario.
        AID id = new AID();
        id.setLocalName("Puerta");
        mensaje.addReceiver(id);
 
        //Añadir el comportamiento
        this.addBehaviour(new SalirClase(this, mensaje));
    }
 
    private class SalirClase extends ProposeInitiator {
        public SalirClase(Agent agente, ACLMessage mensaje) {
            super(agente, mensaje);
        }
 
        //Manejar la respuesta en caso que acepte: ACCEPT_PROPOSAL
 
        protected void handleAcceptProposal(ACLMessage aceptacion) {
            System.out.printf("%s: Solicitud aceptada.\n", this.myAgent.getLocalName());
            System.out.printf("%s: Saliendo de clase...\n", this.myAgent.getLocalName());
        }
 
        //Manejar la respuesta en caso que rechace: REJECT_PROPOSAL
 
        protected void handleRejectProposal(ACLMessage rechazo) {
            System.out.printf("%s: Solicitud denegada.\n", this.myAgent.getLocalName());
            System.out.printf("%s: No salgo de clase.\n", this.myAgent.getLocalName());
        }
    }
}

ProposeRes

package examples.practica5;
 
import javax.swing.JOptionPane;
import jade.core.Agent;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.NotUnderstoodException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.proto.ProposeResponder;
 
public class ProposeRes extends Agent {
 
    protected void setup() {
        System.out.printf("%s: Esperando propuestas...\n", this.getLocalName());
 
        //Creamos la plantilla a emplear, para solo recibir mensajes con el protocolo FIPA_PROPOSE y la performativa PROPOSE
        MessageTemplate plantilla = ProposeResponder.createMessageTemplate(FIPANames.InteractionProtocol.FIPA_PROPOSE);
 
        //Añadimos el comportamiento "responderSalirClase()"
        this.addBehaviour(new ResponderSalirClase(this, plantilla));
    }
 
    //Método que permite al usuario decidir si acepta la propuesta o si la rechaza.
    private boolean checkContent(String agente, String propuesta) {
        if (JOptionPane.showConfirmDialog(null, propuesta, agente + " dice:", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
            return true;
        } else {
            return false;
        }
    }
 
    private class ResponderSalirClase extends ProposeResponder {
 
        public ResponderSalirClase(Agent agente, MessageTemplate plantilla) {
            super(agente, plantilla);
        }
 
        //Preparación de la respuesta. Recibe un mensaje PROPOSE y, según su contenido, acepta o no.
 
        protected ACLMessage prepareResponse(ACLMessage propuesta)
                throws NotUnderstoodException {
            System.out.printf("%s: Proposicion recibida de %s.\n", this.myAgent.getLocalName(), propuesta.getSender().getLocalName());
            System.out.printf("%s: La propuesta es: %s.\n", this.myAgent.getLocalName(), propuesta.getContent());
 
            //Comprueba los datos de la propuesta
            if (ProposeRes.this.checkContent(propuesta.getSender().getLocalName(), propuesta.getContent())) {
                //Aceptación de la propuesta.
                System.out.printf("%s: Dando permiso para salir de clase.\n", this.myAgent.getLocalName());
 
                //Se crea la respuesta al mensaje con la performativa ACCEPT_PROPOSAL, pues se acepta.
                ACLMessage agree = propuesta.createReply();
                agree.setPerformative(ACLMessage.ACCEPT_PROPOSAL);
 
                return agree;
            } else {
 
                //Rechazo de la propuesta.
                System.out.printf("%s: Prohibiendo que se salga de clase.\n", this.myAgent.getLocalName());
 
                //Se crea la respuesta al mensaje con la performativa REJECT_PROPOSAL, pues se rechaza.
                ACLMessage refuse = propuesta.createReply();
                refuse.setPerformative(ACLMessage.REJECT_PROPOSAL);
 
                return refuse;
            }
        }
    }
}


3.6.5 Subscription


El protocolo FIPA-Subscription consiste en que el iniciador le solicita al respondedor permiso para suscribirse, el respondedor procesa esa solicitud y la acepta o la rehecha. Si la acepta envía un mensaje con las condiciones de suscripción, y lo hace de forma continua hasta que sufre un fallo y envía un failure o hasta que el iniciador cancela el envío. Los mensajes que se intercambian son:
  1. Subscribe, solicitando la suscripción.
  2. El respondedor envía un Refuse si no acepta la propuesta de suscripción, un Agree si la acepta y un Not-Understood si hubo algún fallo.
  3. Si la aceptó, después envía un Inform-Result con las condiciones que establezca para la suscripción.
  4. Para detener el envío, o bien sufre un fallo el respondedor y envía un Failure o bien envía un Cancel el iniciador.
  5. Si el proceso se detuvo porque el iniciador envió un Cancel, el respondedor manda un Inform-Done si todo se realizó exitosamente, o un Failure si algo no funcionó correctamente.

A continuación se presenta un ejemplo que hace uso de estas clases.
En este ejemplo el agente que inicia (representado por la clase SubscriptionIni) se suscribirá en el agente que Responde (representado por la clase SubscriptionRes) para que le envíe catálogos periódicamente. Cuando haya recibido 5 números cancelará su suscripción.
Para que funcione correctamente hay que crear el agente MediaMarket de tipo SubscriptionRes (que hay que lanzar primero) y un agente SubscriptionIni que llamaremos como queramos.

SubscriptionIni
package examples.practica5;
 
import jade.core.AID;
import jade.core.Agent;
import jade.domain.FIPANames;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.proto.SubscriptionInitiator;
 
public class SubscriptionIni extends Agent {
 
    protected void setup() {
        //Se crea un mensaje de tipo SUBSCRIBE y se asocia al protocolo FIPA-Subscribe.
        ACLMessage mensaje = new ACLMessage(ACLMessage.SUBSCRIBE);
        mensaje.setProtocol(FIPANames.InteractionProtocol.FIPA_SUBSCRIBE);
        mensaje.setContent("Enviame todos las semanas el catalogo.");
 
        //Se añade el destinatario del mensaje
        AID id = new AID();
        id.setLocalName("MediaMarket");
        mensaje.addReceiver(id);
 
        //Añadir el comportamiento
        this.addBehaviour(new SuscribirCatalogo(this, mensaje));
    }
 
    private class SuscribirCatalogo extends SubscriptionInitiator {
        private int suscripciones = 0;
 
        public SuscribirCatalogo(Agent agente, ACLMessage mensaje) {
            super(agente, mensaje);
        }
 
        //Maneja la respuesta en caso que acepte: AGREE
 
        protected void handleAgree(ACLMessage inform) {
            System.out.println(SubscriptionIni.this.getLocalName() + ": Solicitud aceptada.");
        }
 
        // Maneja la respuesta en caso que rechace: REFUSE
 
        protected void handleRefuse(ACLMessage inform) {
            System.out.println(SubscriptionIni.this.getLocalName() + ": Solicitud rechazada.");
        }
 
        //Maneja la informacion enviada: INFORM
 
        protected void handleInform(ACLMessage inform) {
            System.out.printf("%s: Informe recibido: %s.\n", SubscriptionIni.this.getLocalName(), inform.getContent());
 
            this.suscripciones++;
            if (this.suscripciones == 5) {
                //Enviamos la cancelación de la suscripcion
                this.cancel(inform.getSender(), false);
 
                //Comprobamos que se ha cancelado correctamente
                this.cancellationCompleted(inform.getSender());
            }
        }
 
        //Maneja la respuesta en caso de fallo: FAILURE
 
        protected void handleFailure(ACLMessage failure) {
            //Se comprueba si el fallo viene del AMS o de otro agente.
            if (failure.getSender().equals(myAgent.getAMS())) {
                System.out.println(SubscriptionIni.this.getLocalName() + ": El destinatario no existe.");
            } else {
                System.out.printf("%s: El agente %s falló al intentar realizar la acción solicitada.\n",
                    SubscriptionIni.this.getLocalName(), failure.getSender().getName());
            }
        }
 
        public void cancellationCompleted(AID agente) {
            //Creamos una plantilla para solo recibir los mensajes del agente que va a cancelar la suscripción
            MessageTemplate template = MessageTemplate.MatchSender(agente);
            ACLMessage msg = blockingReceive(template);
 
            //Comprobamos que tipo de mensaje llegó: INFORM o FAILURE
            if (msg.getPerformative() == ACLMessage.INFORM)
                System.out.printf("%s : Suscripcion cancelada con el agente %s.\n",
                    SubscriptionIni.this.getLocalName(), agente.getLocalName());
            else
                System.out.printf("%s: Se ha producido un fallo en la cancelación con el agente %s.\n",
                    SubscriptionIni.this.getLocalName(), agente.getLocalName());
        }
    }
}

SubscriptionRes
package examples.practica5;
 
import jade.core.Agent;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.FIPAAgentManagement.NotUnderstoodException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.proto.SubscriptionResponder;
import jade.proto.SubscriptionResponder.Subscription;
import jade.proto.SubscriptionResponder.SubscriptionManager;
import java.util.HashSet;
import java.util.Set;
 
public class SubscriptionRes extends Agent {
    //Se crea una tabla indexada donde guardaremos las suscripciones realizadas.
    private Set<Subscription> suscripciones = new HashSet<Subscription>();
 
    protected void setup() {
        System.out.println(this.getLocalName() + ": Esperando suscripciones...");
 
        //Se crea una plantilla para que sólo se admitan mensajes del protocolo FIPA-Subscribe
        MessageTemplate template = SubscriptionResponder.createMessageTemplate(ACLMessage.SUBSCRIBE);
 
        //Se añade un comportamiento que cada 5 segundos envía un mensaje a todos los suscriptores.
        this.addBehaviour(new EnviarSemanal(this, (long) 5000));
 
        //Se añade un comportamiento que maneja los mensajes recibidos para suscribirse.
        //Habrá que crear primero el SubscriptionManager que registrará y eliminará las suscripciones.
        SubscriptionManager gestor = new SubscriptionManager() {
 
            public boolean register(Subscription suscripcion) {
                suscripciones.add(suscripcion);
                return true;
            }
 
            public boolean deregister(Subscription suscripcion) {
                suscripciones.remove(suscripcion);
                return true;
            }
        };
        this.addBehaviour(new HacerSuscripcion(this, template, gestor));
    }
 
    //Comprueba la propuesta. En este caso si el contenido del mensaje tiene una longitud mayor que 2, devuelve true
    private boolean compruebaMensaje(String propuesta) {
        return (propuesta.length() > 2);
    }
 
    private class EnviarSemanal extends TickerBehaviour {
        public EnviarSemanal(Agent agente, long tiempo) {
            super(agente, tiempo);
        }
 
        public void onTick() {
            //Se crea y rellena el mensaje con la información que desea enviar.
            ACLMessage mensaje = new ACLMessage(ACLMessage.INFORM);
            mensaje.setContent(String.valueOf(getTickCount()));
 
            //Se envía un mensaje a cada suscriptor
            for (Subscription suscripcion:SubscriptionRes.this.suscripciones) {
                suscripcion.notify(mensaje);
            }
        }
    }
 
    private class HacerSuscripcion extends SubscriptionResponder {
        private Subscription suscripcion;
 
        public HacerSuscripcion(Agent agente, MessageTemplate plantilla, SubscriptionManager gestor) {
            super(agente, plantilla, gestor);
        }
 
        //Método que maneja la suscripcion
 
        protected ACLMessage handleSubscription(ACLMessage propuesta)
                throws NotUnderstoodException {
            System.out.printf("%s: SUSCRIBE recibido de %s.\n",
                SubscriptionRes.this.getLocalName(), propuesta.getSender().getLocalName());
            System.out.printf("%s: La propuesta es: %s.\n",
                SubscriptionRes.this.getLocalName(), propuesta.getContent());
 
            //Comprueba los datos de la propuesta
            if (SubscriptionRes.this.compruebaMensaje(propuesta.getContent())) {
 
                //Crea la suscripcion
                this.suscripcion = this.createSubscription(propuesta);
 
                try {
                    //El SubscriptionManager registra la suscripcion
                    this.mySubscriptionManager.register(suscripcion);
                } catch (Exception e) {
                    System.out.println(SubscriptionRes.this.getLocalName() + ": Error en el registro de la suscripción.");
                }
 
                //Acepta la propuesta y la envía
                ACLMessage agree = propuesta.createReply();
                agree.setPerformative(ACLMessage.AGREE);
                return agree;
            } else {
                //Rechaza la propuesta y la envía
                ACLMessage refuse = propuesta.createReply();
                refuse.setPerformative(ACLMessage.REFUSE);
                return refuse;
            }
        }
 
        //Maneja la cancelación de la suscripcion
 
        protected ACLMessage handleCancel(ACLMessage cancelacion) {
            System.out.printf("%s: CANCEL recibido de %s.\n",
                SubscriptionRes.this.getLocalName(), cancelacion.getSender().getLocalName());
 
            try {
                //El SubscriptionManager elimina del registro la suscripcion
                this.mySubscriptionManager.deregister(this.suscripcion);
            } catch (Exception e) {
                System.out.println(SubscriptionRes.this.getLocalName() + ": Error en el desregistro de la suscripción.");
            }
 
            //Acepta la cancelación y responde
            ACLMessage cancela = cancelacion.createReply();
            cancela.setPerformative(ACLMessage.INFORM);
            return cancela;
        }
    }
}

arriba.png

3.7 Ejercicios


Normas de entrega:

Enunciado:

Crear una comunicación entre el agente Emisor y el agente Receptor que siga este orden:

  1. Emisor -> PING -> Receptor
  2. Receptor -> PONG -> Emisor
  3. Emisor -> PING -> Receptor

Completa el código para realizar el ejercicio.

Emisor.java

import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.*;
 
public class Emisor extends Agent
{
    class EmisorComportaminento extends SimpleBehaviour
    {
        boolean fin = false;
        public void action()
        {
              //Preparandose para enviar un mensaje a receptor
            AID id = new AID();
            id.setLocalName(.......);
 
            // Creación del objeto ACLMessage
            ACLMessage mensaje = new ACLMessage(...........);
 
            //Rellenar los campos necesarios del mensaje y envio
            mensaje.setSender(......());
            mensaje.setLanguage("Español");
            mensaje.addReceiver(id);
            mensaje.setContent("PING 1");
 
            send(mensaje);
 
 
               //Esperando la respuesta
            ACLMessage mensaje2 = .............();
            if (mensaje2!= null)
            {
                //Muestra mensaje de respuesta
                System.out.println("--------------------------------------------");
                System.out.println("--------------------------------------------");
                System.out.println("Origen: Receptor; Mensaje: ");
                System.out.println(mensaje2.toString());
                System.out.println("--------------------------------------------");
                System.out.println("--------------------------------------------");
 
                // Creación del objeto ACLMessage
                ACLMessage mensaje1 = new ACLMessage(ACLMessage.REQUEST);
 
                //Rellenar los campos necesarios del mensaje y envio
                mensaje1.setSender(getAID());
               mensaje1.setLanguage("Español");
                mensaje1.addReceiver(....);
                mensaje1.setContent("PING 2");
 
                send(........);
 
                fin = true;
            }
        }
 
        public boolean done()
        {
            return ...;
        }
    }
    protected void setup()
    {
        addBehaviour(new ..................());
    }
}




Receptor.java

import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.ACLMessage;
 
public class Receptor extends Agent
{
    class ReceptorComportaminento extends SimpleBehaviour
    {
            private boolean fin = false;
            private boolean fin2 = false;
            public void action()
            {
                //Obtiene el primer mensaje de la cola de mensajes
                ACLMessage mensaje = ........();
 
                if (mensaje!= ....)
                {
                    if (fin == true)
                    {
                    //Recibiendo segundo mensaje
                    System.out.println("--------------------------------------------");
                            System.out.println("--------------------------------------------");
                            System.out.println("Origen: Emisor; Mensaje: ");
                            System.out.println(mensaje.toString());
                            System.out.println("--------------------------------------------");
                            System.out.println("--------------------------------------------");
 
                        // Envia constestación
                        ACLMessage respuesta = new ACLMessage( ACLMessage........);
                        respuesta.setContent( "PONG" );
                        respuesta.addReceiver( mensaje.getSender() );
                        send(........);
 
                        fin2 = true;
                    }
                    else
                    {
                //Recibiendo primer mensaje
                System.out.println("--------------------------------------------");
                            System.out.println("--------------------------------------------");
                            System.out.println("Origen: Emisor; Mensaje: ");
                            System.out.println(mensaje.toString());
                            System.out.println("--------------------------------------------");
                            System.out.println("--------------------------------------------");
 
                        // Envia constestación
                        ACLMessage respuesta = new ACLMessage( .........INFORM );
                        respuesta.setContent( "PONG" );
                        respuesta.addReceiver( mensaje.......() );
                        send(........);
 
                        fin = true;
                    }
 
                }else
                {
                    System.out.println("Receptor: Esperando a recibir mensaje...");
                    .....();
                }
            }
            public boolean done()
            {
                return fin2;
            }
    }
    protected void setup()
    {
        addBehaviour(new ...............());
    }
}




arriba.png

3.8 Ejercicio (Comunicación II (segunda parte))



Normas de entrega:



El ejercicio a desarrollar consiste en la combinación de dos de los protocolos de comunicacion vistos anteriormente.

Se deberá completar el codigo de los tres agentes que se indican a continuacion para que lleven a cabo la siguiente actividad:

Simula la peticion de hijo al padre para salir de fiesta, el padre le preguntará a la madre si puede. La madre sacará un
mensaje por pantalla preguntando la respuesta. Si se acepta o deniega se lo informa al padre y este al hijo.

El hijo preguntará al padre mediante el protocolo FIPA-QUERY
El padre preguntará a la madre mediante el protocolo PROPOSE

Orden de ejecucion: Madre->Padre->Hijo

java jade.Boot -gui
java jade.Boot -container madre:Madre
java jade.Boot -container padre:Padre(madre)
java jade.Boot -container hijo:Hijo(padre)

saldra un mensaje de la madre preguntando si se le deja

Madre.java


Padre.java


Hijo.java