Club-INTech / TechTheHighLevel

Refonte du HL
GNU General Public License v3.0
1 stars 0 forks source link

TechTheHighLevel

Ce dépôt a pour but de reconstruire le code Haut Niveau (IA) d'Intech !

Installation - IntelliJ

Prérequis

  1. Installer Java (Par défaut sur beacoup de distribution)
  2. Installer Maven (gestionnaire de dépendance, compilation, ...)
Si un projet est ouvert

File -> New... -> Project from Existing Sources... -> Import Project from external models (Maven)

Laissez tout par défaut

Si sur la fenêtre de démarrage

Import Project -> Choisir le dossier du dépôt cloné -> Import Project from external models (Maven)

Laissez tout par défaut

Installer les dépendances

Alt+F12 pour ouvrir le terminal :

mvn clean install -DskipTests

Vous êtes parrés pour naviguer dans le HL !

Architecture générale

Le HL est composé de trois modules Maven :

TTHL-common

L'architecture de ce projet est orientée micro-services : l'IA se base sur plusieurs modules qui ont tous une tâche bien définie, et qui communiquent entre eux si besoin. Elle est construite de manière à ce qu'elle soit maintenable, performante (dans la limite du language utilisé), et tolérante aux fautes (voir norme ISO/IEC 25010).

Dans le dossier des sources, plusieurs packages regroupent les classes par fonctionnalités :

L'orchestration de tous ces packages et modules se fait de la manière suivante :

TTHL - Architecture générale

TTHL-master

TTHL-slave

Architecture détaillée

Prenez une grande inspiration, on rentre ici dans le détail de l'architecture !

TTHL-common

Utils

Le service de log est une enum : chaque instance représente un canal de journalisation que l'on peut activer et désactiver au besoin, le but étant d'éviter de surcharger le terminal d'informations lorsque l'on se concentre sur une fonctionnalité du robot. Ce service est utilisé quand on souhaite débuger, il s'agit donc d'une surchouche de System.out.print. Il y a trois niveau de log : debug, warning, et critical. Ce dernier niveau de log s'affiche toujours, que le canal spécifié soit activé ou non. Attention à bien initialiser log si le container n'est pas instancié ! Utilisation :

Log.CANNAL.setActive(true);
Log.CANNAL.debug("Debut de la methode A");  // Ca s'affiche !
Log.CANNAL.setActive(false);
Log.CANNAL.warning("Fin de la methode A");  // Ca ne s'affiche pas...
Log.CANNAL.critical("AH GROS BUG");         // Ca s'affiche !

La config est une librairie externe utilisée pour changer des paramètres dans le Haut Niveau sans avoir à recompiler. Par exemple, le rayon du robot ennemi. Il a été developpé par PF Gimenez, un vieux d'Intech aujourd'hui docteur ! Bref, ce service est lui aussi géré par le container. Les paramètres que l'on veut manipuler/retirer/ajouter sont rassemblés dans l'enum ConfigData, qui contient les valeurs des paramètres par défaut. Les valeurs chargées par le container à l'instanciation des services sont présentes dans le fichier config.txt. Attention à utiliser les mêmes clés et types entre le fichier texte et l'enum !

Le container fait office à la fois de factory .ie il instancie les services(toutes les classes qui implémentent l'interface Service), et de gestion des dépendances : lorsque l'on demande un service via la méthode getService(Class class), le container va instancier tous les paramètres du constructeur en tant que service s'ils n'ont pas déjà été instanciés. Utilisation :

Container container = Container.getInstance("Master");
MonService service = container.getService(MonService.class);

"Tu nous parles de service depuis tout à l'heure mais c'est quoi au juste un service ???"

Et bien c'est un singleton offrant des fonctionnalités bien définies ! Dans notre cas c'est une interface qui doit surcharger la méthode public void updateConfig(Config config), qui permet justement de récupérer des valeurs de la config ! On entend par singleton une classe qui n'a qu'un seule instance. Exemple :

ConfigData.java :

import pfg.config.ConfigInfo;

public enum ConfigData implements ConfigInfo {
    PARAM_MONSERVICE(18)
    ;
}

config/config.txt :

...
PARAM_MONSERVICE =              24
...

MonService.java :

import utils.container.Service

public class MonService implements Service {
    private int param;
    public MonService(MonAutreService ah) {...}
    @Override
    public void updateConfig(Config config) {
        this.param = config.getInt(ConfigData.PARAM_MONSERVICE);
    }
}
Connection

Ce service sert à gérer des IO (Input Output) du HL, c'est-à-dire l'échange de messages entre le HL et le LL, mais aussi avec le Lidar, et la communication Master-Slave. Il se base sur l'enum Connection qui répertorie les connections du HL. Après avoir initialiser les connections à l'aide de la méthode initConnections(Connection... connections), on peut simplement envoyer et lire des messages grâce aux autres méthodes :

import connection.ConnectionManager
import connection.Connection

public static void main(String[] args) {
    Container container = Container.getInstance("Master");
    ConnectionManager connectionManager = container.getService(ConnectionManager.class);

    connectionManager.initConnections(Connection.LOCALHOST_SERVER, Connection.LOCALHOST_CLIENT);
    Connection.LOCALHOST_SERVER.send("Hello !");
    Optional<String> m = Connection.LOCALHOST_CLIENT.read();
    String mess;
    if (m.isPresent()) {
        mess = m.get();
    }
}

ATTENTION : Dans le HL, les connections sont initialisées dans le Listener ! (voir plus bas)

Orders

L'order wrapper est un service servant à simplifier l'envoi d'ordres au bas niveau via des méthodes plus simples d'utilisation. Par exemple, plutôt que de devoir écrire, à chaque fois que l'on veut envoyer au bas niveau l'ordre d'avancer d'une certaine distance _Connection.TEENSYMASTER.send("d 100"), on préfère utiliser une méthode du style orderWrapper.moveLenghtwise(100) ! C'est le premier intérêt de l'order wrapper. Pour cela on se base sur des enums qui implémentent l'interface orders.order.Order, et qui typent les chaînes de caractères correspondant aux ordres que l'on envoie au bas niveau. Regardez la classe orders.order.MotionOrder ainsi que dans l'order wrapper pour plus d'informations.

L'order wrapper ne s'occupe pas que de simplifier l'envoie d'ordre au bas-niveau, il est également en charge de la symétrisation des ordres. Pour ne pas avoir à écrire deux version du code, une pour chaque côté de la table, le haut niveau réfléchit toujours du même côté et symétrise les ordres envoyés au bas niveau si nécessaire. C'est au niveau de l'order wrapper que cela se fait. Imaginons que le HL réfléchisse toujours du côté violet, et que l'on utilise la méthode moveToPoint(Vec2 point), avec point[x: 370, y: 800] (voir en annexe pour le repère de la table). Si l'on est du côté violet, l'order wrapper doit envoyé au LL la chaîne de caractères "p 370 800". Si l'on est du côté jaune, il envoie "p -370 800"_.

"Et du coup comment je crée un ordre ?"

La première chose à faire est de se mettre d'accord avec le bas niveau sur la chaîne de caractère associé à l'ordre et le format d'envoi si nécéssaire. Une fois ceci fait, c'est tout simple si c'est un actionneur !

orders.order.ActuatorsOrder.java:

public enum ActuatorsOrder {
    ...
    MON_ORDRE_ACTIONNEUR("ordre LL", 100);
    MON_ORDRE_SYMETRIQUE("ordre LL 2", 100); // S'il a besoin d'être symétrisé
    ...
}

Si l'ordre a besoin d'être symétrisé (si l'actionneur à bouger dépend du côté de la couleur qui nous a été attribuée) :

orders.SymmetrizedActuatorOrderMap.java:

public class SymmetrizedActuatorOrderMap implement Service {
    ...
    private SymmetrizedActuatorOrderMap {
        correspondanceMap.put(ActuatorOrder.MON_ORDRE_ACTIONNEUR, ActuatorOrder.MON_ORDRE_SYMETRIQUE);
    }
    ...
}

Voilà pour un ordre de type actionneur, la méthode useActuator(ActuatorOrder order, boolean waitForCompletion) s'occupe du reste !

"Le reste ? Du genre l'entier à côté de la chaîne de caractère et le booléen waitForCompletion dans le prototype de la méthode que t'as pas expliqué ?"

Ce petit entier est en fait le temps que le HL doit attendre pour que l'action se finisse. Si l'on ne met pas d'entier, l'action est considérée comme immédiate et le HL va continuer sa réflexion et son envoi d'ordres au LL. Cela aboutit souvent à des actions qui se déroulent en même temps, et ce n'est pas toujours souhaitable. Cependant il y a bien des fois où c'est pratique de déplier/replier des actionneurs en même temps ! C'est pourquoi ce petit booléen waitForCompletion existe dans le prototype de la méthode. Mis à false, le HL ne va pas attendre le temps indiqué dans la classe orders.order.ActuatorsOrder avant de passer à la suite.

Le service HookFactory comme son nom l'indique, permet de créer des Hooks ! Un hook est tout simplement un mécanisme qui permet d'effectuer une action en mouvement. C'est le LL qui s'occupe d'exécuter des hooks mais c'est le HL qui crée les hooks et décide s'ils doivent être activés ou non. Pour créer un hook, rien de plus simple :

oders.hooks.HookNames.java:

public enum HookNames {
    ...
    MON_HOOK(1, new VectCartesian(500, 400), 10, Math.PI/2, Math.PI/8, ActuatorsOrder.MON_ORDRE_ACTIONNEUR),
    ...
}

Le hook est maintenant créé ! Mais il faut le configurer, c'est-à-dire dire au LL qu'il existe lors de l'exécution, et l'activer. Par exemple dans la classe Main.java:

public class Main {
    Container container;
    HookFactory factory;
    ...
    public static void main(String[] args) {
        container = Container.getInstance("Master");
        factory = container.getService(HookFactory.class);
        ...
        factory.configureHook(HookNames.MON_HOOK);
        factory.enableHook(HookNames.MON_HOOK);
        ...
        // Do what you want !
    }
}
Data

Cette classe représente la table et contient donc les obstacles et tout ce qu'on peut faire avec, les supprimer ou les bouger par exemple. Cette classe s'appuie donc sur la classe Obstacle est ses classes filles. La principale modification effectuée dans cette classe est l'ajout des obstacles fixes (les élements de jeu dont on connait la position exacte au top départ)

public class Table {
    ...
    private void initObstacle() {
        StillRectangularObstacle monObstacle = new StillRectangularObstacle(
            new VectCartesian(0, 1800), 1600 + 2* this.robotRay, 300 + 2* this.robotRay);
        this.fixedObstacles.add(monObstacle);
    }
    ...
}
Locomotion

TTHL-master

TTHL-slave

Tests

Les tests, indispensables pour la maintenabilité du HL, et permettant d'être efficace pour trouver l'origine de vos bugs lorsque vous développez de nouvelles fonctionnalités, sont découpés ici en trois packages :

Ces tests sont destinés à être exécutés quotidiennement par un bot Jenkins (excépté pour les réels), vous permettant de vite voir si un bug a été introduit par une feature et d'indentifier plus rapidement son origine.

Annexes

TODO : Plan de la table + umls