Squelette d'un programme de robotique (I - La classe Robot)
Cet article constitue le premier d'une série consacrée à la programmation d'applications robotiques en langage Python. Cinq classes seront présentées successivement :
- La classe Robot, traitée dans le présent article. Le plus souvent, cette classe pourra être utilisée telle quelle, l'intelligence de la programmation étant déportée sur les tâches qui y seront associées.
- La classe RobotTask traitée dans un autre article, constitue le squelette de tâches qui seront associées au robot. Elle devra être dérivée pour être adoptée au contexte de l'application.
- La classe RobotError est une classe d'exception propre au module de robotique. Elle peut être utilisée telle quelle ou dérivée pour servir de racine commune à toutes les classes d'exception concernant l'utilisation du module.
- La classe RobotExplorer est une classe dérivée de la classe Robot qui rassemble les élément constitutifs (moteurs et capteurs) ainsi que les constantes géométriques du robot dont la construction est décrite dans l'article « Construction du robot Explorateur ».
- La classe IRControlledTankTask est une classe dérivée de la classe RobotTask qui reprend la programmation du robot Explorer piloté par la télécommande infra-rouge de Lego MindStorm .
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 Robot
Tout les programmes de robotique fonctionnent de la même façon. Cela consiste en une liste de tâches, exécutées en parallèle, constituées d'une boucle sans fin dans laquelle les capteurs du robot sont lus pour déclencher les actions appropriées. C'est pourquoi, disposer d'une classe de base type pour prendre en charge les tâches parallèles permet de ne pas avoir à re-coder systématiquement la même séquence d'instruction. C'est ce que propose la classe Robot.
Dans la majorité des cas, cette classe pourra être utilisée telle-quelle. En effet, l'intelligence des robots se situe dans les tâches qui lui sont associées. Cependant, chaque robot ayant ses propres caractéristiques, tel ou tel moteur, tel ou tel capteur, ou telle géométrie, il est préférable de la dériver pour y ajouter les constantes propres au robot. La classe RobotExplorer est une illustration de ce procédé.
Programmation
Cette classe est structurée comme un dictionnaire de tâches. Les tâches, après avoir été instanciées, sont ajoutées au robot grâce à la méthode add_task(). Et celles-ci sont activées par l'invocation de la méthode run(). En voici le codage :
class Robot():
def __init__(self):
# 1 #
self.__tasks = {}
def add_task(self, task, name):
# 2 #
if not(isinstance(task, RobotTask)):
raise RobotError(RobotError.TYPE_ERROR, repr(task), RobotTask)
# 3 #
if self.running:
raise RobotError(RobotError.ADD_TASK_ERROR)
# 4 #
if task.name in self.__tasks:
self.__tasks[task.name].stop()
self.__tasks[task.name] = task
def run(self):
# 5 #
for key in self.__tasks:
if not self.__tasks[key].running:
self.__tasks[key].start()
def stop(self):
# 6 #
for key in self.__tasks:
if self.__tasks[key].running:
self.__tasks[key].stop()
# 7 #
@property
def running(self):
is_running = False
for key in self.__tasks:
is_running |= self.__tasks[key].running
return is_running
def __init__(self):
# 1 #
self.__tasks = {}
def add_task(self, task, name):
# 2 #
if not(isinstance(task, RobotTask)):
raise RobotError(RobotError.TYPE_ERROR, repr(task), RobotTask)
# 3 #
if self.running:
raise RobotError(RobotError.ADD_TASK_ERROR)
# 4 #
if task.name in self.__tasks:
self.__tasks[task.name].stop()
self.__tasks[task.name] = task
def run(self):
# 5 #
for key in self.__tasks:
if not self.__tasks[key].running:
self.__tasks[key].start()
def stop(self):
# 6 #
for key in self.__tasks:
if self.__tasks[key].running:
self.__tasks[key].stop()
# 7 #
@property
def running(self):
is_running = False
for key in self.__tasks:
is_running |= self.__tasks[key].running
return is_running
Explications
- A la construction, un dictionnaire Python, codé par {}, est créé vide. Celui-ci constitue les dictionnaire des tâches du robot. Il est affecté à la variable d'instance __tasks. Le double souligné indique que la variable est private. C'est à dire qu'elle n'est pas visible de l'extérieur de l'objet, ni dans les classes dérivées.
- La méthode add_task permet d'ajouter une tâche au dictionnaire. Celle-ci doit être une instance d'une classe dérivée de RobotTask. Elle est repérée dans le dictionnaire par son nom (une chaîne de caractères). En effet, il ne faut pas ranger n'importe quoi dans le dictionnaire des tâches. Si l'objet passé en paramètre n'est pas une RobotTask, une exception de classe RobotError est levée.
- Il ne faut pas ajouter de tâche au robot, si celui-ci est déjà démarré (running = True). Si c'est le cas, une exception de classe RobotError est levée. Si aucune exception n'est levée la tâche est insérée dans le dictionnaire.
- Si une tâche de même nom existe déjà dans le dictionnaire, la nouvelle tâche remplace l'ancienne. Mais si cette dernière est en marche, il peut se passer beaucoup de temps avant qu'elle ne soit détruite par le Garbage Collector de Python. Ce qui veut dire que deux tâches risquent de fonctionner en concurrence avec un résultat inattendu. C'est pourquoi, l'ancienne tâche doit être explicitement arrêtée.
- La méthode run() permet de lancer le robot. Toutes les tâches contenues dans le dictionnaire sont démarrée. La commande run() peut être lancée plusieurs fois. Seules les tâches arrêtées sont redémarrées.
- La méthode stop() permet d’arrêter le robot proprement. Toutes les tâches en marche contenues dans le robot sont arrêtées.
- La classe Robot expose une propriété running indiquant que le robot est en marche. On utilise le décorateur Python @property pour que la propriété soit en lecture seule. On considère que le robot est en marche si au moins l'une des tâches qui lui est associée est en marche. Cette propriété peut être utilisée dans un test, comme c'est le cas dans la méthode add_task pour éviter d'ajouter un tâche au robot lorsque celui-ci est en marche en levant une exception.
Utilisation
robot = Robot()
task = MyRobotTask(robot)
robot.run()
time.sleep(180)
robot.stop()
task = MyRobotTask(robot)
robot.run()
time.sleep(180)
robot.stop()
Explications
- Une instance de la classe Robot est créée et affectée à la variable robot.
- Une instance de la classe MyRobotTask est créée et affectée (mais ici ce n'est pas nécessaire) au une variable task. Le robot instancié auparavant est passé en paramètre du constructeur de la tâche. De ce fait, la tache est automatiquement associée au robot. Comme aucun nom n'est précisé pour la tâche, c'est le nom "Main" par défaut qui est utilisé.
- Le robot est démarré par l'invocation de la méthode run(). Comme les tâches fonctionnent en parallèle, la main passe immédiatement à l'instruction suivante. Celle-ci met le programme en attente pendant trois minutes (180 secondes) au bout desquelles le robot est arrêté par l'invocation de la méthode stop().
Commentaires
Enregistrer un commentaire