Framework multi-agent en C++
https://github.com/hdd-robot/gAgent
Auteur : Halim Djerroud
Introduction
gAgent est une framework écrit en C++ qui permet de développer un système multi-agent compatible FIPA1 en C++ et en python.
La spécification FIPA
Architecture gAgent
Dans gAgent, chaque agent est un processus linux. Chaque agent (processus) est composé d’au moins deux threads :
-
Listner : Ce thread est un thread détaché , il est implémenté sous forme de service, il permet de gérer les événements externes , des signaux Linux de
SIGRTMIN + 2
àSIGRTMIN + 7
. -
Controler : Ce thread est un thread détaché aussi, il permet de gérer les verrous.
Premier pas
Le plus petit programme gAgent:
#include <gagent/AgentCore.hpp>
using namespace gagent;
int main(int argc, char* argv[]) {
AgentCore::initAgentSystem();
...
AgentCore::stopAgentSystem();
return EXIT_SUCCESS;
}
#include <gagentAgentCore.hpp>
: Inclure le moteur principale de gAgent.
AgentCore::initAgentSystem()
: Permet d’initialiser gAgent (lire le fichier config).
AgentCore::stopAgentSystem()
: Permet de libérer les ressources gAgent.
Créer un premier Agent
Les agents gAgent peuvent être dans d’un de ces états :
-
Setup: À ce stade l’agent existe en mémoire mais sont état est inconnu (UNKNOWN). Pour activer l’agent il faut appeler la méthode
doInit()
-
Suspendu: La méthode
doSuspend()
permet de suspendre un agent qui est dans l’état Active, dans ce nouveau état (Suspendu) l’agent arrête tout exécution. Seule la méthodedoActivate()
permet le faire sortir de cet état pour revenir dans l’état active. -
En attente: La méthode
doWait()
permet de mettre l’agent en état d’attente d’un événement (généralement un message) puis il revient dans sont état active lors de la réception de l’événement ou-bien on peut le forcer son retour dans l’état active avec la méthodedoWake()
-
Active: Dans cet état l’agent est en cours d’exécution.
-
Transit: La méthode
doMove()
permet la migration des agents d’une plateforme à une autre. -
Fin: La méthode
doDelete()
permet d’arrêter d’un agent en exécution et libérer les ressources.
#include <gagent/Agent.hpp>
#include <gagent/AgentCore.hpp>
using namespace gagent;
class myAgent: public Agent {
public:
myAgent() :Agent() {};
virtual ~myAgent() {}
void setup() {
...
}
};
int main(int argc, char* argv[]) {
AgentCore::initAgentSystem();
myAgent* g = new myAgent();
g->init();
sleep(3);
g->doWait();
sleep(3);
g->doActivate();
sleep(3);
g->doMove();
sleep(3);
g->doSuspend();
sleep(3);
g->doActivate();
sleep(3);
g->doWait();
sleep(3);
g->doWake();
sleep(3);
g->doDelete();
delete g;
AgentCore::stopAgentSystem();
return EXIT_SUCCESS;
}
Les méthodes :
-
setup()
: invoqué lors de l’initialisation de l’agent. -
takedown()
: invoquée avant qu’un agent ne quitte la plateforme.
Les comportements d’un agent gAgent
Pour qu’un agent exécute une tâche, ou plusieurs, if faut auparavant définir ces tâches. Les tâches sont appelées behaviours (ou comportements en français) sont des instances de la classe Behaviour ou une des ses sous classes.
Pour qu’un agent exécute une tâche il doit lui l’attribuer un ou plusieurs Behaviour par la méthode addBehaviour(Behaviour b)
de la classe Agent (voir l’exemple qui suit).
gAgent implémente chaque Behaviour dans un thread, si plusieurs Behaviour sont implémenté par l’agent, alors ces Behaviours sont exécutés en parallèles.
Chaque Behaviour doit obligatoirement implémenter les deux méthodes suivantes :
-
action()
: l’utilisateur doit implémenter les opérations à exécuter par le Behaviour; -
done()
: qui indique si le Behaviour en question a terminé son exécution ou pas.
Les deux autres méthodes suivantes, dont l’implémentation n’est pas obligatoire mais qui peuvent s’avérer utiles :
-
onStart()
: appelée juste avant l’exécution de ma méthodeaction()
; -
onEnd()
: appelée juste après la retournement de true par la méthodedone()
.
Si besoin de savoir quel est le propriétaire d’un Behaviour, et cela peut être connu par le membre this_agent
du Behaviour en question.
Exemple d’implémentation d’un Behaviour:
#include <iostream>
#include <stdio.h>
#include <gagent/Behaviour.hpp>
#include <gagent/Agent.hpp>
#include <gagent/AgentCore.hpp>
using namespace gagent;
class myCycle: public CyclicBehaviour {
public:
myCycle(Agent* ag) :CyclicBehaviour(ag) {
}
void action() {
std::cout << "Coin " << std::flush;
sleep(1);
}
};
class myAgent: public Agent {
public:
myAgent() :Agent() {};
virtual ~myAgent() {}
void setup() {
myCycle* bb = new myCycle(this);
addBehaviour(bb);
}
};
int main() {
AgentCore::initAgentSystem();
myAgent* g = new myAgent();
g->init();
AgentCore::syncAgentSystem();
AgentCore::stopAgentSystem();
return 0;
}
Résultat :
Coin Coin Coin Coin Coin Coin Coin
gAgent propose les Behaviours suivants:
-
OneShotBehaviour
-
SimpleBehaviour
-
CompositeBehaviour
-
CyclicBehaviour
-
WakerBehaviour
-
ParallelBehaviour
-
SequentialBehaviour
-
FSMBehaviour
Les Behaviours simples
C’est un Behaviour qui donner au
Ils sont composés de Behaviours : OneShotBehaviour, CyclicBehaviour.
...
class mySimpleBehavior: public SimpleBehaviour {
public:
mySimpleBehavior(Agent* ag) : SimpleBehaviour(ag) {}
unsigned int i = 0;
void onStart(){
std::cout << "Je sais compter de 1 a 10 "
<< std::endl << std::flush;
i=0;
}
void action() {
i++;
std::cout << i << std::endl << std::flush;
}
bool done(){
if (i==10){
std::cout << "J ai fini de compter :) ."
<< std::endl << std::flush;
return true;
}
return false;
}
};
class myAgent: public Agent {
public:
myAgent() :Agent() {};
virtual ~myAgent() {}
void setup() {
mySimpleBehavior* b1 = new mySimpleBehavior(this);
addBehaviour(b1);
}
};
Résultat :
Je sais compter de 1 a 10
1
2
3
4
5
6
7
8
9
10
J ai fini de compter :) .
OneShotBehaviour
Le Behaviour OneShotBehaviour permet d’exécuter une tâche une et une seule fois puis il se termine. La classe OneShotBehaviour implémente la méthode done()
et elle retourne toujours true.
...
using namespace gagent;
class myBehavior: public OneShotBehaviour {
public:
myBehavior(Agent* ag) : OneShotBehaviour(ag) {
}
void onStart() {
std::cout << " -- start -- " << std::endl << std::flush;
}
void action() {
std::cout << "Coin " << std::endl << std::flush;
}
void onEnd() {
std::cout << " -- end -- " << std::endl << std::flush;
}
};
class myAgent: public Agent {
public:
...
void setup() {
myBehavior* bb = new myBehavior(this);
addBehaviour(bb);
}
};
int main() {
...
myAgent* g = new myAgent();
g->init();
...
}
Résultat :
-- start --
Coin
-- end --
Un exemple avec deux Behaviours :
...
class myBehavior1: public OneShotBehaviour {
public:
myBehavior1(Agent* ag) : OneShotBehaviour(ag) {}
void action() {
std::cout << "Comportement 1 " << std::endl << std::flush;
}
};
class myBehavior2: public OneShotBehaviour {
public:
myBehavior2(Agent* ag) : OneShotBehaviour(ag) {}
void action() {
std::cout << "Comportement 2 " << std::endl << std::flush;
}
};
class myAgent: public Agent {
public:
...
void setup() {
myBehavior1* b1 = new myBehavior1(this);
addBehaviour(b1);
myBehavior2* b2 = new myBehavior2(this);
addBehaviour(b2);
}
};
int main() {...
Résultat:
Comportement 1
Comportement 2
Cyclic Behaviour
Le Cyclic Behaviour permet d’exécuter la méthode action() de façon continue indéfiniment.
...
class myCycle: public CyclicBehaviour {
public:
myCycle(Agent* ag) :CyclicBehaviour(ag) {
}
void action() {
std::cout << "Coin " << std::flush;
sleep(1);
}
};
class myAgent: public Agent {
public:
myAgent() :Agent() {};
virtual ~myAgent() {}
void setup() {
myCycle* bb = new myCycle(this);
addBehaviour(bb);
}
};
int main() {
AgentCore::initAgentSystem();
myAgent* g = new myAgent();
g->init();
AgentCore::syncAgentSystem();
AgentCore::stopAgentSystem();
return 0;
}
Résultat:
Coin Coin Coin Coin Coin Coin Coin Coin Coin Coin ...
Les Behaviours planifiés
WakerBehaviour
Le WakerBehaviour permet d’exécuter la méthode onWake()
après un délai passé en paramètre en millisecondes.
...
using namespace gagent;
class myWakerBehaviour: public WakerBehaviour {
public:
myWakerBehaviour(Agent* ag) : WakerBehaviour(ag,50000) { }
void onWake(){
std::cout << "Termine " << std::endl << std::flush;
}
};
class myAgent: public Agent {
public:
myAgent() :
Agent() {
};
virtual ~myAgent() {
}
void setup() {
myWakerBehaviour* b = new myWakerBehaviour(this);
addBehaviour(b);
}
};
int main() {
...
g->init();
...
}
Résultat:
//apres une attente de 5 secondes.
Termine
TickerBehaviour
Le TickerBehaviour est implémenté pour qu’il exécute sa tâche périodiquement par la méthode onTick(). La durée de la période est passée comme argument au constructeur en millisecondes.
...
class myWakerBehaviour: public WakerBehaviour {
public:
myWakerBehaviour(Agent* ag) : WakerBehaviour(ag,5000) { }
void onWake(){
std::cout << " Termine " << std::endl << std::flush;
this_agent->doDelete();
}
};
class myTickerBehaviour: public TickerBehaviour {
public:
myTickerBehaviour(Agent* ag) : TickerBehaviour(ag,1000) { }
void onTick(){
std::cout << " tictac " << std::endl << std::flush;
}
};
class myAgent: public Agent {
public:
myAgent() :
Agent() {
};
void setup() {
myWakerBehaviour* b3 = new myWakerBehaviour(this);
addBehaviour(b3);
myTickerBehaviour* b4 = new myTickerBehaviour(this);
addBehaviour(b4);
}
};
int main() {
AgentCore::initAgentSystem();
myAgent* g = new myAgent();
g->init();
AgentCore::syncAgentSystem();
AgentCore::stopAgentSystem();
return 0;
}
Résultat :
tictac
tictac
tictac
tictac
tictac
Termine
La communication entre les agents
Le plateforme gAgent
Monitoring
gAgent propose un outil de monitoring sur une console. pour lancer le monitoring il faut appeler le programme agentmonitor
. C’est un service qui permet de recevoir des messages des agents en utilisant le protocole UDP.
L’adresse IP et le PORT sont définis en paramètres sinon le programme contente de lire le fichier de configuration config.cfg
dans lequel sont ces paramètres sont définis.
$agentmonitor --help
Allowed options:
--help produce help message
--port arg port
--ip arg Ip adress
On peut tester cet outil avec la commande Unix nc
comme suite echo ’hello’ | nc -u <ip> <port>
.
exemple :
echo ’hello’ | nc -u 127.0.0.1 40013
000001 : hello
L’objectif de cet outil est permettre à l’utilisateur de surveiller le système durant le son fonctionnement.
Les agent peuvent envoyer les messages (en C++) sur le monitor comme suite :
this_agent->sendMsgMonitor(" Hello ");
Résultat :
000001 : EbcrjZlp -> Start agent PID : 13029 000002 : EbcrjZlp -> tictac 000003 : EbcrjZlp -> tictac 000004 : EbcrjZlp -> tictac 000005 : EbcrjZlp -> tictac 000006 : EbcrjZlp -> tictac 000007 : EbcrjZlp -> Stop agent PID : 13029
1. http://www.fipa.org↩︎