Ecrire le code d’un agent

Il est maintenant temps de définir vos propres agents. Avec un langage de programmation comme Java, cette définition prend la forme d’une classe. Nous allons donc voir ici comment doit être écrit une telle classe.

1 - l'interface AgentU et l'adaptateur AgentUAdapter

Pour qu’une classe définissant un type d’agent soit reconnue comme telle par la librairie SKUAD, il faut que cette classe implémente (directement ou indirectement) l’interface AgentU (package: skuad.agentu). En effet, cette interface définie toutes les méthodes dont le système SKUAD a besoin pour manipuler en interne les objets agents.

L’interface AgentU définit exactement 14 méthodes, voici leur signature :

//Cycle de vie de l'agent
public void start(AgentUDescriptor aud, int ctx); //invoqué au démarrage de l'agent
public void stop(AgentUDescriptor aud, int ctx); //invoqué lors de la mise à l’arrêt de l'agent
public void event(AgentUDescriptor aud, int code, int ctx); //évènement subit par l'agent

//Exécution du comportement de l'agent
public void behavior(AgentUDescriptor aud, Pulse pulse);

//Devices attachés à l'agent
//évènement d'attachement de device
public void notifyPlugDevice(AgentUDescriptor aud, String slot, Device device);
public void notifyUnplugDevice(AgentUDescriptor aud, String slot);
public void notifyChangeDevice(AgentUDescriptor aud, String slot, Device device);
//Ecoute des devices
public void stateDevice(AgentUDescriptor aud, String slot, Device dev, boolean ready, boolean config_changed);
public void sensorChange(AgentUDescriptor aud, String slot, int sensor_id, Value value);
//Réponse aux lectures des capteurs
public void sensorReading(AgentUDescriptor aud, String query_label, Value value);

//Avatars associés à l'agent
public void notifyPlugAvatar(AgentUDescriptor aud, String name, Avatar avatar, int contexte);
public void beforeUnplugAvatar(AgentUDescriptor aud, String name, Avatar avatar, int contexte);
public void notifyUnplugAvatar(AgentUDescriptor aud, String name, int contexte);
public void receive(AgentUDescriptor aud, Avatar avatar, MailBox box, Message mess);

Rassurez vous, vous n’avez pas besoin de comprendre le rôle de chacune de ces méthodes pour le moment. Nous les découvrirons au fur et à mesure des prochains tutoriels. Nous pouvons même écrire le code d’un agent en laissant vide chacune de ces méthodes. Bien sûr, les agents produits à partir d’une telle classe ne feront pas grand chose, mais cela ne provoquera aucune erreur. Et si nous nous donnons déjà la peine d’exploiter une ou deux de ces méthodes, cela suffira déjà pour que les agents qui en résulteront puisse présenter un potentiel intéressant.

Comme vous pouvez le remarquer, le premier argument de chacune de ces méthodes est un objet AgentUDescriptor (package: skuad.agentu). C’est un objet important pour l’agent car c’est cet objet qui va lui permettre d’exprimer toutes ses capacités. Mais pour l’agent simple que nous aurons à écrire dans ce tutoriel nous n’aurons pas besoin de l’utiliser, nous reviendrons donc plus tard sur l’usage de cet argument.

Du fait que AgentU est une interface, même si nous n’avons besoin de définir qu’une ou deux méthodes pour notre première classe agent, nous serons quand même obligé d’écrire le code de toutes les autres méthodes quitte à placer aucune instruction dans leur définition. Heureusement, pour nous éviter cette labeur il existe la classe AgentUAdapter (package: skuad.agentu.util). Cette classe est un adaptateur (adapter en anglais) pour l’interface AgentU. Cela veut dire que c’est une classe qui implémente toutes les méthodes de cette interface, avec un code vide pour chacune de ses méthodes. A première vue, une classe comme celle-ci, sans aucun traitement dans ses méthodes, peut paraître inutile. Mais en fait elle est très pratique car elle permet d’alléger l’écriture nécessaire à l’implémentation d’une interface. En effet, dans notre cas, pour définir notre classe agent il nous suffit d’hériter de la classe AgentUAdapter sans qu’on est besoin d’écrire la moindre méthode dans le code de notre classe. Ou tout du moins, en écrivant seulement les méthodes qui nous intéressent. Car notre classe bénéficient de la définition des autres méthodes de l’interface AgentU via le lien d’héritage qu’elle possède avec la classe AgentUAdapter.

Pour bien comprendre cela, écrivez donc le code d’une classe MyAgentHelloBye qui doit définir un agent similaire à ceux de type (c’est à dire « de la classe ») AgentHelloBye que nous avons utilisé dans le tutoriel : Gérer l’exécution d’un agent. Pour rappel, les agents de ce type doivent écrire un message quand leur exécution commence et se termine. Vous ferez en sorte que ces messages soit : « Bonjour, je suis un agent MyAgentHelloBye » au début de l’exécution, et « Au-revoir, j’étais un agent MyAgentHelloBye » à la fin de l’exécution de l’agent.

Pour écrire cette classe vous utiliserez la classe AgentUAdapter afin d’avoir moins de méthode à définir. Et vous vous focaliserez plus particulièrement sur les 2 premières méthodes (start(…) et stop(…)) de l’interface AgentU. Vous testerez cette classe en écrivant un programme qui va créer un agent de ce type et lancer son exécution, comme nous avons appris à le faire dans le tutoriel Gérer l’exécution d’un agent.

A vous de jouer !

Une fois que vous avez réussi à écrire ce programme, ou si vous n’y parvenez pas, vous pouvez comparez votre code à la proposition de solution ci-dessous.

afficher la solution
Code source du fichier MyAgentHelloBye.java :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import skuad.agentu.*;
import skuad.agentu.util.*;

public class MyAgentHelloBye extends AgentUAdapter {

  public void start(AgentUDescriptor aud, int ctx) {
    System.out.println("Bonjour, je suis un agent MyAgentHelloBye");
  }

  public void stop(AgentUDescriptor aud, int ctx) {
    System.out.println("Au-revoir, j’étais un agent MyAgentHelloBye");
  }

  public static void main(String[] args) {
    ServerAU server = ServerAU.newServer(null);

    int agent_id = server.createAgent(MyAgentHelloBye.class, "toto");

    server.runAgent(agent_id);
  }
}

Vous noterez que dans cette écriture nous avons placé la fonction main(…) directement dans la classe MyAgentHelloBye. Vous n’êtes pas obligé de procéder ainsi, mais avec cette astuce cela nous évite de créer une autre classe pour écrire le programme de test. En quelque sorte nous donnons ainsi deux rôles à la classe MyAgentHelloBye : le premier rôle est celui de définir un nouveau type d’agent; et le second rôle est celui d’être un programme qui permet de tester l’exécution d’un agent de ce type.

Notez également qu’il est important de bien respecter la signature des méthodes que l’on redéfinie dans notre classe, et qui correspondent à celles définies dans la classe AgentUDapater et l’interface AgentU. Une toute petite erreur à ce niveau est cela ne fonctionne plus, sans pour autant qu’un message d’erreur nous signale clairement l’origine du problème.

En effet, le simple fait d’écrire Start(…), avec un S majuscule, au lieu de start(…) et cela revient à définir une nouvelle méthode de nom Start qui est différente, et donc qui vient en plus, de la méthode de nom start. Comme il n’est pas interdit de définir de nouvelles méthodes, cela ne provoque pas d’affichage d’erreur. Mais cette nouvelle méthode Start ne sera pas utilisée par le système et le programme ne fonctionnera donc pas comme prévu.

Il faut donc être très vigilant quand on redéfinie une méthode qui est censée existait dans la classe mère ou l’interface qu’on implémente. Quand on utilise Eclipse, une bonne façon de constater qu’une telle écriture est correcte est d’observer la présence du petit triangle dans la marge gauche au niveau de la première ligne de la méthode :

Ce petit triangle indique que la définition vient surcharger (donc remplacer) une méthode qui existe au niveau supérieure. Vous devriez donc toujours le voir quand vous redéfinissez une méthode existante.

 
2 - la spécification des slots de l'agent

Quand vous avez lancé l’exécution de votre programme précédent, vous avez certainement remarqué les 2 messages -warning- qui sont apparus sur les 2 premières lignes de la console :

Ces alertes vous indiques que la classe agent MyAgentHelloBye ne comporte pas de spécification de slot sociale et de slot physique. Ce qui est normale car nous n’avons pas encore vu comment faire ces spécifications. La finalité de tels slots est de permettre à l’agent d’interagir socialement (en échangent des messages avec d’autres agents) et physiquement (en manipulant des dispositif matériel). Tous les agents n’ont pas forcément de ces deux formes d’interaction, et certains n’en ont même besoin d’aucune des deux. C’est justement le cas du type agent MyAgentHelloBye  qui ne fait qu’écrire des messages dans la console système.

Cependant la majorité des agents utiliserons ces slots, et les classes agents définirons donc le plus souvent les spécifications correspondantes. Et pour éviter qu’une erreur de syntaxe conduise à ce que ces spécifications ne soient pas reconnu comme tel par le système, des messages d’alertes sont affichés dans les cas où aucune spécification social et aucune spécification physique ne sont trouvées. Ainsi le concepteur de la classe est clairement informé de cette situation. C’est ce qui s’est produit pour notre classe MyAgentHelloBye.

Pour éviter l’affichage de ces alertes nous devons donc ajouter les spécifications des slots dans la classe MyAgentHelloBye. Et puisque que ce type d’agent n’a pas besoin de ces slots, nous allons nous limiter à indiquer qu’il n’existe aucun slot. Ainsi, même si la classe agent ne comporte pas de slot, il faut l’indiquer explicitement par une spécification de slot vide.

Dans le code de la classe agent, ces spécifications sont les suivantes :

  • les slots physiques : ils sont définis par un attribut final static, de type String, et de nom : physical_specif
  • les slots sociaux: ils sont définis par un attribut final static, de type String, et de nom : social_specif

Nous reviendrons plus tard sur la syntaxe précise qu’il faut utiliser au niveau de ces deux chaines de caractères pour déclarer et configurer les slots physiques et sociaux. Pour le moment nous pouvons nous limiter à la valeur chaine vide pour chacun de ces deux attributs afin d’indiquer qu’il n’existe aucun slot pour cette classe d’agent.

Modifier donc le code de votre classe MyAgentHelloBye sur la base de ces explications, et relancer votre programme afin de vérifier que les messages d’alertes ne s’affiche plus.

A vous de jouer !

Une fois que vous avez réussi à écrire ce programme, ou si vous n’y parvenez pas, vous pouvez comparez votre code à la proposition de solution ci-dessous.

afficher la solution
Nouveau code source du fichier MyAgentHelloBye.java :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import skuad.agentu.*;
import skuad.agentu.util.*;

public class MyAgentHelloBye extends AgentUAdapter {

  public final static String physical_specif = ""; //aucun slot physique
  public final static String social_specif = ""; //aucun slot social

  public void start(AgentUDescriptor aud, int ctx) {
    System.out.println("Bonjour, je suis un agent MyAgentHelloBye");
  }

  public void stop(AgentUDescriptor aud, int ctx) {
    System.out.println("Au-revoir, j’étais un agent MyAgentHelloBye");
  }

  public static void main(String[] args) {
    ServerAU server = ServerAU.newServer(null);

    int agent_id = server.createAgent(MyAgentHelloBye.class, "toto");

    server.runAgent(agent_id);
  }
}

 
3 - les évènements de changement d'état

Pour parvenir a reproduire plus précisément ce que fait la classe AgentHelloBye que nous avons utilisé dans le tutoriel  Gérer l’exécution d’un agent, il nous faut encore aller un petit plus loin dans l’usage des méthodes de l’interface AgentU.

Il nous reste en effet à faire parler notre agent quand celui-ci entre dans l’état pause, et quand il reprend ensuite son exécution. Il nous faut donc être en mesure de réagir au moment ou notre agent change d’état. Pour faire cela dans notre classe MyAgentHelloBye  nous allons devoir redéfinir la méthode suivante de l’interface AgentU :

public void event(AgentUDescriptor aud, int code, int ctx)

Cette méthode est en effet déclenchée par le système à chaque fois que l’agent change d’état, mais elle peut l’être aussi pour d’autre type d’événement que nous étudierons plus tard. Les déclenchement suite à un changement d’état pause/resume seront ceux pour lesquels l’argument code prendra l’une des valeurs suivantes :

  • AgentU.PAUSED : signale que l’agent vient d’être mis en pause.
  • AgentU.RESUMED : signale que l’agent vient de reprendre son exécution suite à une mise en pause.

L’argument ctx indique le contexte dans lequel le changement d’état s’est produit (la situation à l’origine de l’événement). Nous reviendrons sur son utilité dans un prochain tutoriel.

A l’aide de ces explications, essayez donc de compléter le code de votre classe MyAgentHelloBye de sorte que :

  • le message « Je suis un agent MyAgentHelloBye, et je vais dormir, bonne nuit ! » s’affiche quand l’agent est mis en pause.
  • et, que le message « Bonjour ! Je suis un agent MyAgentHelloBye et je me réveille. » s’affiche quand l’agent reprend son exécution.

Testez ceci en reprennent la dernière version du programme que vous avez produit à la fin du tutoriel Gérer l’exécution d’un agent (qui effectue donc des mises en pause et de reprises d’exécution périodique, et qui exploite la classe AgentLogger afin de monitorer l’activité de l’agent) en utilisant cette fois votre la classe MyAgentHelloBye à la place de la classe AgentHelloBye.

A vous de jouer !

Une fois que vous avez réussi à écrire ce programme, ou si vous n’y parvenez pas, vous pouvez comparez votre code à la proposition de solution ci-dessous.

afficher la solution