Squelette d'un programme de robotique (II - La classe RobotTask)

Cet article constitue le second d'une série consacrée à la programmation d'applications robotiques en langage Python. Dans cette série, cinq classes sont présentées successivement :
Les classes Robot, RobotTask et RobotError sont rassemblées dans un module Python : robot.py. Ce module, téléchargeable à ici, doit être inclus dans le projet dans lequel il est utilisé. Bien que les exemples soient donnés dans le contexte d'un robot Lego MindStorm EV3, ce module peut servir de base pour n'importe quel robot programmé en Python.

La classe RobotTask

La classe RobotTask constitue la classe de base de toutes les tâches robotiques pouvant être affectées un robot de classe Robot. Comme elles sont toutes structurées de la même façon, il est utile de disposer d'un modèle facile à dériver pour les adapter à chaque contexte nécessaire. D'autant, que celles-ci devant fonctionner en parallèle, y embarquer la technique de MultiThreading, offre un avantage supplémentaire important.

Programmation

Une tâche est toujours structurée de la façon suivante :
  • Une série d'instructions exécutées une seule fois au démarrage de la tâche. Ces instructions sont exposées par la méthode setup() qui peut être surchargée dans les classes dérivées.
  • Une autre série d'instructions exécutées dans une boucle. Ces instructions, étant exposées par la méthode loop(), qui doit être surchargée pour indiquer pour chaque capteur lu, le traitement approprié à effectuer. 
De plus, la boucle doit être exécutée dans une thread pour fonctionner en mode parallèle.
class RobotTask():

    # 1 #
    DEFAULT_NAME = "Main"

    def __init__(self, robot, name=DEFAULT_NAME):
        # 2 #
        if not(isinstance(robot, Robot)):
            raise RobotError(RobotError.TYPE_ERROR, repr(robot), Robot)
        # 3 #
        self.__robot = robot
        self.__is_running = False
        self.__name = name
        # 4 #
        robot.add_task(self)

    # 5 #
    def __task(self):
        self.setup()
        while self.__is_running:
            self.loop()

    # 6 #
    def start(self):
        self.__is_running = True
        task = Thread(target=self.__task)
        task.start()

    # 7 #
    def stop(self):
        self.__is_running = False

    # 8 #
    def setup(self):
        pass

    # 9 #
    def loop(self):
        self.stop()

    # 10 #
    @property
    def name(self):
        return self.__name

    # 11 #
    @property
    def owner(self):
        return self.__robot

    # 12 #
    @property
    def running(self):
        return self.__is_running

Explications

  1. DEFAULT_NAME est une variable de classe qui contient le nom de la tâche par défaut ("Main").
  2. Deux paramètres sont passés à la construction de la tâche : le robot auquel la tâche est attachée et son nom. Si le paramètre robot n'est pas une instance de Robot, une exception est levée.
  3. Les paramètres de construction sont enregistrés dans des variables d'instance. Le double-souligné indique que ces variables sont private. C'est-à-dire qu'elle ne sont pas visibles de l’extérieur. En revanche elle sont exposées en lecture seule sous forme de property pour être visible sans pouvoir être modifiées de l'extérieur (point 10, 11 et 12).
  4. La tâche est automatiquement attachée au robot auquel elle appartient par une invocation de la méthode add_task sur celui-ci.
  5. La méthode __task() constitue la structure principale d'une tâche. Comme toutes les tâches robotiques, elle commence par exécuter une fonction setup() une seule fois et une fonction loop() dans une boucle contrôlée par la variable __is_running. Le double-souligné indique qu'elle n'est pas visible de l'extérieur. En effet, ce n'est pas nécessaire, car elle est lancée en parallèle par la méthode start().
  6. La méthode start() permet de contrôler l'exécution de la tâche __task(). Une instance de Thread permet de la lancer en parallèle en invoquant la méthode start() de celle-ci.
  7. La méthode stop() permet d'arrêter la tâche en passant la variable __is_running.
  8. La méthode setup() peut être surchargée dans les classes dérivées. Elle permet de définir la liste d'instructions à exécuter une seule fois au début de la tâche.
  9. La méthode loop() doit être surchargée dans les classes dérivées pour définir la liste des instructions à exécuter dans la boucle.
  10. La propriété name permet d'exposer le nom de la tâche à l'extérieur en lecture seule.
  11. La propriété owner permet d'exposer le robot propriétaire de la tâche à l'extérieur en lecture seule.
  12. La propriété running permet d'exposer l'état de la tâche (en marche ou à l'arrêt) à l'extérieur en lecture seule.

Utilisation

Cette classe ne peut pas être utilisée telle-quelle. En effet, comme les fonctions setup() et loop() n'implémentent que l'instruction pass, elles ne font strictement rien. En revanche, elle contient le squelette de toutes les tâches robotiques.
L'utilisation de cette classe consiste à la dériver en surchargeant les méthodes setup() et loop().
La classe IRControlledTankTask, pour contrôler un robot à chenille par la télécommande infra-rouge, est un exemple de cet usage.

Conclusion

Les classes Robot et RobotTask utilisent lèvent des exceptions pour contrôler le passage des paramètres. Afin de distinguer ces exceptions, un classe leur est dédiée. L'article suivant présente la classe RobotError implémentant les exceptions levées par les classe Robot et RobotTask.

Commentaires

Posts les plus consultés de ce blog

Connecter ev3dev2 à Internet en WiFi

Connecter Visual Studio Code à un robot MindStorm EV3 avec ev3dev-browser

Installer les modules EV3DEV2 sur Python