Les slots physiques des agents

Comme nous l’avons vu dans le tutoriel Manipulation de base des agents/Ecrire le code d’un agent, les capacités d’interaction d’un agent sont spécifiées par des slots pouvant être de deux natures :

  • physique : pour caractériser les capacités d’interacions physiques de l’agent. C’est à dire sa faculté à opérer des actions ou des lectures sur des dispositifs marériels.
  • social : pour caractériser les capacités d’interactions sociales de l’agent. C’est à dire sa faculté à communiquer, explicitement mais aussi implicitement (par observation), avec d’autres agents.

Dans ce tutoriel nous allons nous intéresser aux interactions physiques. Nous allons donc voir comment déclarer les slots physiques dans une classe agent, et qu’elles sont les concéquecences de ces déclarations dans le fonctionnement des agents qui seront produits sur la base de cette classe.

Attention : pour ce tutoriel vous devez utiliser une librairie SKUAD de version v0.1_b06 ou supérieure !
Si besoin suivez ce tutoriel : Mettre à jour la librairie SKUAD.

 
1 - Déclaration des slots physiques

Pour déclarer les slots physiques d’un agent il faut agir au niveau de la définition du type de l’agent, donc dans l’écriture du code de sa classe. Dans ce code il faut définir un attribut final static de type String et de nom physical_specif :

  public final static String physical_specif = "XXXX";    
   //XXXX -> la chaine de définition des slots physiques, chaîne vide si aucun

Cet attribut prend pour valeur une chaîne de définition des slots physiques. Cette valeur peut-être la chaîne vide dans le cas ou ce type d’agent ne doit comporter aucun slot physique. Notez que dans ce dernier cas il est préférable d’effectuer quand même cette déclaration avec une valeur vide plutôt que ne pas déclarer l’attribut (le système affiche un message d’alerte si cet attribut n’est pas présent).

La syntaxe de la chaîne de définition des slots physiques est la suivante :

« definition_slot1; definition_slot2; definition_slot3; definition_slotN; »

Avec chaque definition_slotX de la forme:

  • pour un slot non optionnel : name_slotX : type_deviceX
  • pour un slot optionnel : name_slotX : type_deviceX , optional

La valeur name_slotX spécifie le nom du slot, vous pouvez utiliser une chaîne de caractère alphanumérique quelconque (sans espace) à ce niveau. La valeur type_deviceX précise le type de dispositif matériel (device en anglais) attendu sur ce slot. Notez que cette valeur n’est qu’indicative, dans les faits il se pourra trés bien qu’un dispositif d’un type différent soit câblé sur à ce slot durant l’exécution d’un agent. Voici quelques valeurs de type possible :

  • BUTTON : pour un dispositif de type bouton, donc un capteur renvoyant des signaux push quand une action est appliquée sur le bouton.
  • LIGHT : pour un dispositif de type lumière, donc un actionneur acceptant les commandes on/off.
  • LOGGER : pour un dispositif de type console de log, donc un actionneur acceptant les commandes log.
  • TEMPERATURE : pour un dispositif de type thermomètre, donc un capteur émettant des valeurs de température.
  • ROVER : pour un dispositif de type mobile, donc un actionneur acceptant des commandes de déplacement forward/backward/left/right/stop.
  • … etc.

Enfin, le mot clé optional peut être ajouté pour le cas où le slot n’est pas critique pour l’agent. On entend par là que le comportement de l’agent reste cohérent, et peut opérer normalement, même si aucun dispositif n’est associé au slot. On dit alors que le slot est optionnel, et on ajoute le mot clé optional dans sa déclaration pour marquer cette caractéristique. Nous examinerons plus en détail les possibilités à ce niveau dans un futur tutoriel.

Pour améliorer la lisibilité de la définition des slots physiques il est conseillé de définir chaque slot sur une ligne différente en liant l’ensemble des lignes par une concaténation (opérateur + en Java). Par exemple, pour définir un ensemble de 2 slots non optionnel de nom bouton et lumiere, avec les types respectifs BOUTTON et LIGHT on écrira l’affectation de l’attribut physical_specif comme ceci :

  public final static String physical_specif =    
                                      "bouton  :  BUTTON ;"
                                  +   "lumiere :  LIGHT  ;" ;

 
2 - Câblage des slots physiques de l'agent

Quand un agent dispose de déclaration de slots physiques cela signifie que durant son existence il pourra manipuler des dispositifs matériels par l’intermédiaire de ces slots. Mais ce n’est pas une certitude pour autant. En effet, pour concrétiser ces possibilités de manipulations il faut également que des dispositifs existants s’associent effectivement, à un moment ou un autre, à ces slots. On parle alors de câblage pour qualifier ces associations (ou de plug en anglais).

Les cablâges qui doivent être opéré sur les slots d’un agent sont déterminés par des règles de cablâge (plug rule en anglais) qui sont des directives d’association slot <-> dispositif qui doivent être définies spécifiquement pour un agent donnée. Ces règles sont fixées via un appel à l’une des méthodes addPhysicalPlugRule(…) de l’objet ServerAU qui exécute l’agent. Il existe 4 variantes de cette méthode :

 public int addPhysicalPlugRule(int agentId, String name_slot, DataReader rule)
 public int addPhysicalPlugRule(int agentId, String name_slot, String rule)
 public int addPhysicalPlugRule(int agentId, String name_slot, Value rule)
 public int addPhysicalPlugRule(int agentId, String name_slot, PhysicalPlugRule rule)

Ces variantes se distinguent au niveau du type de donnée qui permet de décrire la règle de câblage à appliquer (argument de nom rule). Pour le moment nous allons nous limiter à celle où la règle est décrite par une simple chaîne de caractère, donc celle-ci :

 public int addPhysicalPlugRule(int agentId, String name_slot, String rule)

La syntaxe de cette chaine est la suivante :

    String rule = " { "
     +   " node :   filtre_node ,"   //filtrage de niveau hote
     +   " spot :   filtre_spot , "  //filtrage de niveau spot
     +   " device : filtre_device "  //filtrage de niveau device
     + " } ";

C’est ainsi un tableau associatif décrit sous forme textuelle (dans une syntaxe très proche du format JSON que vous connaissez peut-être). Ce tableau associatif comporte 3 champs : node, spot et device. Chacun d’eux est facultatif. Leur valeur du champ spécifie le filtre à appliquer au niveau correspondant à l’étiquette du champ :

  • l’étiquette node : établie un filtrage au niveau des nœuds du réseau (donc au niveau de l’ensemble des machines).
  • l’étiquette spot : établie un filtrage au niveau des contextes d’exécution (donc des programmes, sachant qu’une même machine peut abriter plusieurs programmes).
  • l’étiquette device : établie un filtrage au niveau des devices (donc des dispositifs matériels).

L’ensemble des champs d’une règle élabore ainsi un filtrage combiné sur plusieurs niveaux. Cela permet par exemple de cibler un dispositif correspondant à un type particulier (règle de niveau device), mais qui en plus est piloté par un programme ayant une spécificité précise (règle de niveau spot) lui même hébergé sur une machine donnée (règle de niveau node).

La syntaxe d’un filtre peut prendre deux formes :

  • soit le mot : ALL. Cette valeur signifie qu’aucun filtrage ne sera opéré sur ce champ (donc toutes les cibles existantes sur ce niveau de filtrage seront des candidates potentielles). Cette valeur sera aussi celle considérée pour les étiquettes de champ absentes dans la règle.
  • soit un tableau à deux valeurs de la forme : [ critère , valeur ]. Ce tableau spécifie un filtre qui exprime que le critère doit être de la valeur indiquée.

Les critères de filtrage possibles dépendent de l’étiquette du champ :

  • pour le champ node, les critères possibles sont : NAME ou IP
  • pour le champ spot les critères possibles sont : NAME , PORT ou ID (id global du spot)
  • pour le champ device les critères possibles sont : NAME , TYPE ou ID

Ces critères correspondent à :

  • NAME : le nom donnée à l’élément (au node, spot ou au device)
  • IP : l’ip (l’adresse réseau) de la machine
  • PORT : le port d’écoute SKUAD du programme (nous verrons prochainement que sur une machine donnée, chaque programme SKUAD est associé à un port d’écoute unique, aussi ce numéro de port peut être utilisé comme l’identifiant local d’un programme SKUAD).
  • ID : ce peut être l’identifiant global d’un spot (nous reviendrons sur cela un peu plus bas), ou l’identifiant d’un device
  • TYPE : désigne le type d’un device. Les valeurs possibles ici sont les mêmes que celles que nous avons vu, un peu plus haut, au sujet de la syntaxe de définition des slots physiques d’un agent. Ainsi une valeur pour le critère TYPE peut être l’un des mots : LIGHT, BUTTON, LOGGER, ROVER, …

Voici quelques exemples de règles afin de résumer ces différentes possibilités d’écriture :

 String rule1 = " { "
    + "  node : ALL ,"              //tous les hotes
    + "  spot : ALL ,"              //tous les spots
    + "  device : [ TYPE , LIGHT] " //seulement les lumières
    + " } ";
 
 String rule2 = " { device : [ TYPE , LIGHT] }"; //identique à rule1
 
 String rule3 = " { "
    + "  node : ALL  ,"               //tous les hotes
    + "  spot : [ ID , uYYa9L ] ,"    //le spot d’id global uYYa9L
    + "  device : [ TYPE , BUTTON] "  //seulement les boutons
    + " } ";

Ci dessus, les règles rule1 et rule2 expriment le même filtrage car les champs node et spot sont absents dans la règle rule2 et la valeur qui leur sera appliquée par défaut est ALL. Ces deux règles expriment un filtrage qui va cibler n’importe quel dispositif de type lumière qui existera sur le réseau.

La règle rule3 est un peu plus spécifique. Seul les dipositifs de type bouton, portés par le programme dont l’identifiant global (unique) est uYYa9L, seront ciblés.

Notez que si une règle donne lieu à plusieurs dispositifs candidats, un seul d’entre eux sera associé au slot (selon un choix arbitraire). Car un slot ne peut être associé qu’a un seul device à la fois.

 
3 - Pause pratique

Je sens qu’après toute cette lecture, vous êtes pressé d’en découdre. Mais il faudra être encore un peu patient avant d’être pleinement opérationnel sur les aspects que nous venons de voir. Néanmoins, nous pouvons tout de même faire une pause en nous distrayant avec une petite mise en pratique.

A la fin de ce tutoriel nous allons créer un agent qui fera des actions sur un dispositif. Je vous propose donc que nous voyons déjà comment mettre en place le dispositif en question. Cela ne va pas être compliqué, car nous allons utiliser un dispositif virtuel : une lumière dessiné dans une fenêtre de l’écran. Ce dispositif sera donc un programme, et celui-ci existe déjà dans la librairie SKUAD. Il s’agit du programme : tools.uda.DLightUDP.

Pour lancer l’exécution de ce programme ouvrez une console système et placez-vous dans votre dossier SKUAD. Nous allons utiliser le lanceur SKUAD (le programme start) avec l’argument DLight qui est un alias (un raccourcis) qui désigne le programme tools.uda.DLightUDP. Vous devez donc taper la commande suivante :

  > java   start   DLight  

La fenêtre suivante va alors s’afficher :

Il s’agit de la représentation visuelle du device virtuel que vous venez de lancer. Ce device virtuel s’exécute dans un programme SKUAD, donc un spot, et vous pouvez prendre connaissance de l’identifiant globale de ce spot, car ce programme affiche cette valeur dans la console de lancement. Donc observez bien ce qui est écrit dans cette console, cela va vous servir dans un petit moment 😉

 
4 - Réaliser une interaction physique

Une fois qu’une règle de câblage est fixée pour l’un des slots d’un agent, le système d’exécution va garantir que dès qu’un device sera compatible avec les filtres définis dans la règle, ce device sera automatiquement associé au slot correspondant. On parlera alors de plug physique pour qualifier cet événement, et le code de l’agent sera notifié via un appel à sa méthode notifyPlugDevice(…) (définie par l’interface AgentU).

Inversement, si pour une raison ou pour une autre, le device n’est plus disponible (par exemple la machine qui supporte son exécution s’est arrêtée), alors le slot de l’agent se verra libéré et la méthode notifyUnplugDevice(…) (définie par l’interface AgentU) sera déclenchée sur l’agent.

Enfin, un dernier cas de figure peut être que le device occupant un slot soit remplacé par un autre device. Cela peut arriver par exemple si la règle de câblage du slot subit une modification qui conduit à changer le device candidat sur ce slot. Si cela se produit, le nouveau device candidat remplace le précédent dans l’association avec le slot et la méthode notifyChangeDevice(…) (définie par l’interface AgentU) sera déclenchée sur l’agent.

Il est aussi possible de consulter à tout moment la présence d’un device sur l’un des slots de l’agent. Il suffit pour cela d’utiliser l’une des 2 méthodes suivantes sur son descripteur (objet AgentUDescriptor) :

 public boolean isPhysicalPlugged(String slot_name);
 public Device getDevice(String slot_name);

La méthode getDevice(…) retourne l’objet de type Device (package: skuad.uda) qui correspond au dispositif actuellement associé au slot de nom slot_name. Si ce slot n’est associé à aucun device on récupère alors la valeur NULL. Nous reviendrons plus en détails sur ces objets Device dans de futurs tutoriels. Pour le moment sachez que ces objets sont la représentation, dans le code, d’un dispositif matériel, et que vous pouvez manipuler ces dispositifs en utilisant les méthodes de ces objets Device.

En particulier, pour un dispositif qui représente une lumière, qui possède donc un type de valeur LIGHT, il est possible d’allumer et d’éteindre la lumière en utilisant les appels suivants :

     device.action(0, 1); //on allume la lumière
     device.action(0, 0); //on éteint la lumière

Dans ce code la variable device contient un objet Device de type LIGHT. Le premier argument de la méthode action(…) désigne la commande sur laquelle agir, ici la valeur 0 cible la commande d’allumage/extension de la lumière. Et le deuxième argument correspond à la valeur à appliquer sur cette commande : 1 pour allumer, et 0 pour éteindre.

 
5 - Mise en pratique

Votre objectif dans cette mise en pratique est de créer une classe agent de nom AgentBlink qui spécifie un slot physique de nom « lumiere » et de type « LIGHT ». Ce type d’agent doit définir un comportement périodique de période 3 secondes. A chaque pulsation du comportement vous devez tester si le slot « lumiere » est câblé (i.e. s’il est associé à un device), et si c’est le cas vous devez déclencher une action sur le device correspondant pour l’allumer et l’éteindre alternativement à chaque pulse.

Transformez cette classe en programme qui doit : créer un agent de type AgentBlink, lancer son exécution et lui ajouter une règle de câblage permettant de sélectionner le device lumière virtuel produit par le programme tools.uda.DLightUDP que vous avez lancé dans la pause pratique ci-dessus.

Compilez et testez ce programme. Observez bien l’image de la lumière virtuel, c’est bientôt noël 😉

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

 
6 - Réagir au signal d'un capteur

Le dispositif lumière que nous avons utilisé ci-dessus est un device destiné à recevoir un ordonne afin de produire une action dans l’environnement. On qualifie les dispositifs qui ont cette finalité d’actuateur (ou encore d’actionneur). A l’opposé, certains dispositifs ne produisent pas d’action dans l’environnement, mais une « lecture » d’information. Et quand cette information varie ils déclenchent un signal pour notifier ce changement aux entités qui se sont mis à leur écoute. Ces devices sont appelés des capteurs. Ils peut aussi exister des devices qui disposent des deux capacités : action/captation, et on qualifie ces derniers de dispositif mixte.

Dans la section 4 nous avons appris à envoyer un ordre à un actionneur (plus précisément une lumière). Nous allons maintenant voir comment il faut procéder pour se mettre à l’écoute d’un capteur, et ainsi recevoir les notifications de changement qu’il émet.

Quelque soit le nature du device (actionneur/capteur ou mixte), il faut toujours procéder de la même façon pour l’associer à un agent. Donc, comme nous l’avons vu précédemment pour le device lumière : il faut déclarer un slot d’accueil dans la classe de l’agent ; puis spécifier des règles de câblages qui vont conduire à ce que le device se plug à l’agent dès que cela sera possible durant l’exécution.

Comme nous l’avons vu dans la section 4, dans le code de l’agent on prend connaissance que le câblage est réalisé soit : implicitement via l’invocation automatique de la méthode notifyPlugDevice(…) (interface AgentU); soit explicitement en appelant la méthode isPhysicalPlugged(…) du descripteur de l’agent (objet AgentUDescriptor). On récupère ensuite l’objet device correspondant avec la méthode getDevice(…) du descripteur de l’agent. Mais autant ces étapes de prise de connaissance du câblage et de récupération de l’objet device sont indispensable pour l’usage d’un device de nature actuateur, autant nous n’en n’avons pas forcément besoin pour exploiter un capteur.

En effet, l’interface AgentU dispose de la méthode suivante qui est dédiée à l’écoute des notifications des capteurs associés à l’agent :

public void sensorChange(AgentUDescriptor aud, String slot, int sensor_id, Value value);

Le système SKUAD déclenche automatiquement cette méthode à chaque fois qu’une notification est émise par un device capteur, ou mixte, associé à l’un des slots de l’agent. La valeur remontée par le capteur est décrite par l’argument value. Le type Value (package skuad.util.value) permet d’encapsuler une information de type quelconques. Ici cette information du capteur peut être de l’un des types suivants int / float ou byte[]. Pour consulter le type de la valeur il faut utiliser la méthode getType() de l’objet value, et la réponse correspondra à l’une des constantes suivantes : Value.INT, Value.FLOAT ou Value.BYTES. En fonction de ce type la valeur devra être récupérée avec l’une des méthodes de conversion fournit par la classe Value, ici il s’agira des méthodes : toInt(), toFloat() ou toBytes().

Donc, cela veut dire que pour exploiter un capteur vous n’avez globalement que 3 choses à faire:

  1. Définir un slot physique d’accueil pour le capteur dans la classe de l’agent.
  2. Enregistrer les règles de câblage qui vont permettre d’associer le device sur le slot défini ci-dessus.
  3. Surcharger la méthode sensorChange(…) pour y placer le code du traitement à opérer en réaction au signal du capteur.

 
7 - Mise en pratique

Pour cette mise en pratique nous allons utiliser un device de nature capteur. Ce sera le device virtuel fournit par le programme tools.uda.DButtonUDP, dont l’alias est DButton pour le lanceur SKUAD. Ce programme fonctionne comme pour celui du device virtuel lumière utilisé dans la section 3 :

  > java   start   DButton   

La seule différence ici est que ce programme n’affiche pas l’identifiant global de son spot dans la console. Mais si vous avez besoin de cette information vous pouvez l’obtenir à l’aide de l’utilitaire tools.hubudp.wit. Cet utilitaire réalise un scan de l’ensemble des spots présents sur le réseau et affiche leurs informations (dont leur identifiant global). Pour lancer cet utilitaire vous pouvez utiliser le lanceur SKUAD avec l’alias wit et l’option -no_exit (vous devrez utiliser les touches Ctrl+C pour fermer ce programme):

  > java   start   wit  -no_exit  

Le signal de notification émis par le device virtuel tools.uda.DButtonUDP est une valeur de type int qui vaudra alternativement 0 et 1 à chaque fois que le bouton sera actionné par l’utilisateur.

Votre objectif dans cette mise en pratique est de créer une classe agent de nom MyAgentOnOff qui spécifie 2 slots physiques : l’un de nom « lumiere » et de type « LIGHT »; l’autre de nom « bouton » et de type « BUTTON ». Ce type d’agent doit réagir au signal de changement de valeur émis par le device câblé sur le slot « bouton », et cette réaction doit être de répercuter cette valeur sur la commande d’allumage du device câblé sur le slot « lumière » de l’agent (si slot est effectivement cablé à un device !).

Transformez cette classe en programme qui doit : créer un agent de type MyAgentOnOff, lancer son exécution et lui ajouter :

  • une règle de câblage sur le slot « lumière » permettant de sélectionner le device lumière virtuel produit par un programme tools.uda.DLightUDP.
  • une règle de câblage sur le slot « bouton » permettant de sélectionner le device bouton virtuel produit par un programme tools.uda.DButtonUDP.

Compilez et testez ce programme en lancent (avant/pendant en fonction des règles de câblage que vous aurez spécifiées) les programmes lumière et bouton virtuels. Chaque action sur le bouton devrait normalement conduire à changer l’état de la lumière.

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