Outils d'utilisateurs

Outils du Site


informatique:simu14:code-detail

Le code en détail !

Détail des dossiers et fichiers

Dossier simulateur :

  • Dossier engine :

Le simulateur utilise 2 moteurs : un graphique et un physique. Pour des raisons de commodité, ces 2 moteurs ont été regroupés. Ainsi les objets manipulés dans le simulateur ont des propriétés graphiques et physiques. Le dossier engine regroupe donc le code d'appel aux méthodes des deux 2 moteurs et permet de rendre transparent ces appels pour le reste du simulateur.

  • Dossier geometry :

Plus utilisé dans la version actuelle du simulateur.

  • Dossier gui :

Regroupe le code de la GUI.

  • Dossier map :

Regroupe tous les fichiers qui ont un lien avec la map : le fond, le xml de position des objets sur la carte, mais aussi le code qui charge et créée les objets.

  • Dossier objects :

Regroupe tous les types d'objets utilisés sur la map (tout ce qui peut bouger sur l'air de jeu, dont les robots).

  • Dossier pymunk-4.0.0 :

Moteur physique (on l'a mis sur le git directement à cause des soucis d'installations qu'on a eu).

Concernant les fichiers qui sont dans le dossier racine :

  • comSimu.py :

Interface de communication. Récupère les ordres envoyés par l'IA et appel les méthodes adéquates pour le traitement.

  • debug.py :

Inutilisé.

  • define.py :

Fichier de définition des constantes du simulateur.

  • hokuyo.py :

Simule le comportement de l'hokuyo.

  • mainSimu.py :

Main du simulateur.

  • match.py :

Inutilisé.

  • processIA.py :

Fichier qui s'occupe de communiquer avec l'IA à travers un pipe. C'est aussi ici que les ordres sont récupérés en brute et envoyé à comSimu.py.

  • test.py :

Regroupe le code à exécuter quand on lance le simulateur sans démarrer l'IA.

Les moteurs et les objets

L'essentiel du simulateur réside dans les moteurs graphique et physique. A des fins de simplification, le fonctionnement de ces moteurs aura été abstrait pour n'en garder que les fonctionnalités intéressantes pour notre utilisation. L'affichage graphique et la physique de tous les objets du simulateur ont été rassemblés dans un objet abstrait “EngineObject”.

Pour faire simple, cet objet dispose d'un certain nombre de propriétés essentielle (voir le détail dans le code pour avoir la liste exhaustive : simulateur/engine/engineobject.py) :

  • Type de collision
  • Masse
  • Position initiale
  • Couleur
  • Couche (utile pour gérer les collisions à différents niveaux, voir plus bas)
  • Liste des extensions

La physique et le graphique étant mêlés, on la distinguera de la sorte :

  • Shape = forme de l'objet
  • Body = physique de l'objet

La classe fournie aussi quelques méthodes de base pour manipuler l'objet (récupérer sa position, créer et supprimer des extensions, etc.)

L'implémentation des objets se fait à l'aide des 3 classes filles, qui permettent d'afficher à l'écran :

  • Un cercle (classe “EngineObjectCircle”)
  • Un segment (classe “EngineObject”, seulement utilisée pour simuler les bords de la map)
  • Un polygone (classe “EngineObjectPoly”)

Quelques mots sur des éléments utiles qui découlent directement de ces classes.

Un objet, plusieurs extensions

On l'aura, le simulateur dispose de 3 types d'objets différents. Mais comment faire si l'on veut une forme un peu particulière, comme un cône par exemple ? La solution se trouve dans les extensions. En effet, chaque objet peut posséder une ou plusieurs extensions, qui sont tout simplement d'autres objets, avec leur propre masse, couleur, couche, etc.

Les extensions peuvent être ajoutées à un objet, mais aussi retirées selon le bon vouloir du développeur. Généralement les extensions sont très utiles pour simuler des composants amovibles du robot, comme des bras. A certains moment le robot les déploie (on crée l'extension) et à d'autres il les replie (on la supprime).

Une autre utilisation intéressante des extensions est de pouvoir changer la forme ou la couleur d'un objet. En effet, la librairie ne permet pas de modifier le graphique d'un objet dans une même boucle d'événements. Concrètement, si vous voulez transformer un rectangle en triangle, cela sera impossible. Du coup, soit vous détruisez l'objet pour le reconstruire, soit vous lui ajoutez une extension. Il en va de même pour le changement de couleur.

Attention : un objet ne peut pas être supprimé tant qu'il possède des extensions. Il faudra d'abord supprimer toutes les extensions et ensuite vous pourrez supprimer l'objet.

La gestion des couches (layers)

L'un des gros problèmes du simulateur est qu'il simule un environnement 2D, alors que nos robots évoluent dans un environnement 3D. On pourrait palier à ce problème en développant un nouveau simulateur en 3D, mais cela implique de longs temps de développement.

Une autre solution est la gestion des couches, ou layers. En effet, on placera chaque objet dans une couche, et seuls les objets d'une même couche peuvent interagir entre eux (concrètement si on a 2 objets dans 2 couches différentes, s'ils se touchent, il n'y aura pas de collision).

Par défaut, tous les objets apparaissent dans toutes les couches (paramètre layers = -1). Mais dans la version 2014 du simulateur, certains objets sont dans des couches séparées. Les objets “feu” et les objets “foyers” sont ainsi dans des couches différentes afin qu'on puisse les superposer.

Quid des collisions

Lorsque 2 objets d'une même couche se touchent, il y a collision. Le moteur physique possède un gestionnaire de collision, basé sur des méthodes call back, ce qui permet d'effectuer certaines actions lorsque certains objets entre en collision.

Concrètement, chaque objet possède un type de collision (propriété “colltype”) sous forme de constante déclarée dans le fichier define. Lors d'une collision entre 2 objets, le gestionnaire est appelé. Il possède une liste des couples de type de collision avec la méthode à appeler.

L'ajout de ces méthodes se fait dans le fichier engine/mainengine.py, il faut ajouter un nouveau handler de collision sous la forme : self.physicsengine.add_collision_handler(COLLTYPE_1, COLLTYPE_2, method_a_appeler)

Concernant les méthodes, il est utile de conserver le modèle déjà existant (car il faut entre autre rechercher les 2 objets qui sont entrés en collision).

Conclusion

Partant de ces quelques classes on arrive à avoir un simulateur pleinement fonctionnel. Bien entendu, on pourra fortement améliorer le simulateur, notamment pour tous les aspects physique qui ne sont presque pas gérés. Pour se faire, se référer à la documentation des libraires de chacun des moteurs.

La GUI

Comme indiqué dans la section qui parle de la GUI, cette dernière est divisée en 4 principaux frames. Le découpage est discutable, tout comme le code de la GUI qui est largement améliorable (c'est une première version, surtout pour tester TKinter plus que pour faire un développer propre d'une GUI). Toujours est-il que la GUI est découpée et qu'on retrouve la même trame dans chaque partie :

  • Un fenêtrage fixé en dimensions (la GUI a été “optimisée” pour une résolution de 1366*768)
  • Un affichage qui se fait à l'aide de frames dans les frames
  • Une méthode de pull des données,(1 par frame, mise dans des threads dédiés)
  • Des textes sous forme de StringVar

L'idée de départ de la GUI était de pouvoir envoyer des commandes au robot (sans passer par l'IA donc), mais aussi d'afficher des données du simulateur. Cela ne peut malheureusement pas se faire d'une autre manière que d'avoir une méthode qui boucle indéfiniment et qui va lire les données qu'on veut du simulateur pour mettre à jour les StringVar (permettant ainsi de ne pas avoir à gérer le rafraichissement de l'affichage avec les nouvelles valeurs).

Du reste, c'est une GUI classique avec tous les problèmes que ça entraine (vous l'aurez remarqué, je ne suis pas un fan des GUI, ni de TKinter d'ailleurs…).

Le Viznavgraph ou graphe de navigation

Outre l'ajout de la GUI, l'autre apport majeur de la version 2014 du simulateur est l'ajout de la visualisation du graphe de navigation de chaque robot de façon dynamique (de base l'affichage était statique, l'utilisateur décidait où placer ses robots et il pouvait voir si un chemin existait).

La question qu'il est bon de se poser est : mais qu'est-ce qu'un graphe de navigation ?

Pour répondre à cette question, quelques mots sur le fonctionnement de l'IA de notre robot. Fort de deux télémètres lasers, il nous est possible d'avoir la position des ennemis en temps-réel (ou presque). Grâce à ces données, l'IA est en mesure de déterminer, pour un objectif fixé, si la position peut être atteinte ou non.

Il est utile de préciser qu'en 2014, bien peu d'équipes fonctionnaient de la sorte. En effet, plutôt que de calculer en avance le chemin que le robot doit prendre en fonction de la position des robots adverses, il est plus courant de se rendre à la position et de s'arrêter si l'on détecte un robot à l'aide de capteurs de proximité.

De plus, dans notre IA, la map est subdivisée en polygones convexes qui reflètent l'espace libre pour le robot. On se base sur ces polygones pour déterminer le chemin que le robot doit prendre pour se rendre de déplacer d'un point A à un point B.

Et comme il est extrêmement difficile de visualiser ce chemin avant que le robot se soit déplacé, un petit outil a été développé en 2012 pour afficher le graphe de navigation et le chemin que le robot prendrait pour se déplacer.

En gros, le graphe ressemble à ça (l'espace noir étant l'espace où le robot peut circuler, alors que le blanc est celui où ça lui est impossible) :

Remarque : on affiche simplement le graphe de navigation, la position actuelle du robot nous importe peu (elle importe pour la recherche de chemins, mais ça c'est un autre problème).

L'intégration du graphe dans le simulateur a permis de le coupler avec les données de position de l'ensemble des robots. Cela permet d'avoir un graphe dynamique, qui affichera donc à chaque instant l'espace libre pour chacun de nos robots (il y a un graphe pour le gros robot et un autre graphe pour le petit robot, étant donné que les 2 robots n'ont pas le même diamètre).

Pour activer le graphe de navigation, il faut cliquer sur le radio bouton “vizgraph” tout en haut de la GUI.

Attention : l'affichage du graphe consomme énormément de ressources. Il va sans dire que le fait de l'actualiser régulièrement fait grimper la consommation processeur de l'ordinateur de façon assez impressionnante : ne vous étonnez donc pas si l'affichage n'est pas fluide ;)

La communication

La communication entre le simulateur et l'IA est un point épineux du simulateur. En effet, l'architecture que nous avons adoptée est très particulière et est liée au fait que le protocole de communication que nous utilisions entre les robots est un protocole maison qui est très limité (néanmoins il est aussi très efficace !!).

Toujours est-il que pour avoir une communication entre le simulateur et l'IA, nous avons dû opérer plusieurs changements :

  • Le simulateur envoie et reçois les mêmes ordres que ceux qui transitent en réel à travers le protocole (heureusement que c'est comme ça d'ailleurs)
  • L'IA possède un paramètre pour savoir si elle est en présence du vrai protocole ou alors s'il s'agit du simulateur qui envoie les ordres.
  • C'est le simulateur qui se charge de lancer l'IA en tant que subprocess.

Comme tout cela est un peu compliqué, nous allons détailler et expliquer comment tout ça fonctionne concrètement.

Lorsque le simulateur se lance, il regarde quel IA doit être lancée (= quelle couleur). Le lancement de l'IA se fait par l'appel à la classe ProcessIA. Cette classe va se charger de créer le processus de communication (classe Communication du fichier comSimu.py), ainsi que de lancer l'IA à travers un subprocess.

Pour faire court, un subprocess est un nouveau processus. Dans ce dernier on lance l'IA à proprement parler, et on lui donne en paramètre le PID du simulateur. Cela va nous permettre d'établir la communication entre les 2 processus, via un pipe.

Pour ceux qui ne savent pas ce que c'est, un pipe est grossièrement un “tunnel” qui relie 2 processus. Chaque processus peut lire ou écrire dans le pipe. A noter que la lecture se faire à l'aide d'un thread dédié, car on n'est pas avertis lorsque quelque chose a été écris, il faut continuellement scruter le tunnel.

Donc si on résume, toute la communication entre le simulateur et l'IA se fait à l'aide d'un pipe, où chacun des deux processus va écrire et lire dedans.

Les données envoyées par l'IA et qui sont lues dans le simulateur sont traitées par la classe Communication. Cette dernière va parser les ordres pour appeler les méthodes adéquates. Les ordres ont tous la même forme et sont directement liés au protocole que nous avons développé :

ADRESSE | ORDRE | ARGUMENTS

Sachant que ces 3 données sont des constantes numériques. Ainsi pour pouvoir décoder les ordres, il faut aller jeter un œil au fichier qui regroupe la valeur des constantes (coupe14/libs/com_C/serial_defines.h).

Ainsi, si on veut par exemple que le petit robot exécute un ordre GOTO à la position (50,100), on aura dans le protocole : ADDR_TIBOT_ASSERV A_GOTO 27 50 100 où 27 désigne le numéro de l'ordre envoyé.

A noter que tous les ordres ont un numéro, qui correspond à l'ordre dans lequel les ordres sont envoyés. Lorsqu'un ordre possède des arguments, le premier sera toujours le numéro de l'ordre. Cela permet d'avoir un système d'acquiescement : lorsque l'action a été effectuée, l'actionneur renvoie le numéro de l'ordre à travers le protocole, ce qui permet à l'IA de comprendre que l'ordre a bien été exécuté.

Le détail des arguments pourra être trouvé dans le code des actionneurs du simulateur (fichiers robot.py, minirobot.py ou bigrobot.py).

Pour aller plus loin : De nombreux commentaires ont été mis dans le code du simulateur, n'hésitez pas à aller les consulter pour comprendre davantage le fonctionnement de ce dernier.

informatique/simu14/code-detail.txt · Dernière modification: 2014/08/17 21:31 par tfuhrman