Club robotique de Sophia-Antipolis

Accueil > Projets, études > Nos réalisations > Youpi > Youpi 2.0 > Youpi meets Blockly

Youpi meets Blockly

lundi 28 novembre 2016, par Eric P.

Introduction

Un des buts de Youpi est de se prêter à diverses démonstrations et animations. On l’a vu [1]en train d’obéir à des commandes saisies via une application Web ou un Minitel, de résoudre tout seul des problèmes comme les Tours de Hanoï, mais quid de le programmer d’une manière accessible au non-informaticiens ?

Eh bien c’est chose possible maintenant, grâce à l’aide indirecte de Google.

Programmation graphique

Avant d’entrer dans le vif du sujet, précisons un peu le but recherché.

Il s’agit de permettre à des non-spécialistes (i.e. non-programmeurs) de définir une logique de comportement du bras (séquence de mouvements, logique de décision, itérations...) sans devoir apprendre un quelconque langage de programmation à base de mots-clé et d’instructions cabalistiques.

C’est typiquement ce que LEGO a popularisé dès 1996 avec son environnement de programmation graphique pour les kits robotiques LEGO Mindstorms RCX, puis NXT et maintenant EV3. Basés sur le moteur LabView de National Instruments, dont la réputation n’est plus à faire dans le domaine professionnel des applications de type contrôle-commande, cet environnement permet au roboticien en herbe de définir un algorithme de comportement de son robot en assemblant des éléments graphiques symbolisant les actions élémentaires et les structures d’exécution. Il se base sur un paradigme temporel et fait usage du pilotage par flux de données également (héritage de LabView). Le résultat est le suivant :

D’autres approches ont vu le jour ces dernières années pour proposer des outils faciles d’accès servant de support à l’initiation à la programmation. Un des plus célèbres est Scratch, fruit de travaux du MIT. A la différence de l’environnement Mindstoms, Scratch met plus l’accent visuel sur la structure des algorithmes, la temporalité étant induite par la juxtaposition verticale des blocs.

Scratch a été exploité par de nombreux développeurs pour l’utiliser comme outil de programmation d’autre chose que des animations et interactions graphiques sur le PC (son domaine de départ), et entre autres des robots. Si on met de côté les applications se contentant de télé-commander un robot [2] depuis le PC exécutant le programme Scratch, ce qui offre un intérêt somme toute limité pour nous, la clé du problème est la génération d’un code source (ou équivalent) à partir de la logique exprimée par l’assemblage de la symbolique graphique.

Un grand nombre d’initiatives, pas toujours heureuses d’ailleurs, a ainsi vu le jour, et il existe par exemple des outils permettant de programmer des cartes Arduino en générant le code C soumis au compilateur depuis les schémas Scratch. Malgré toute la considération que méritent ces réalisations, quasiment toutes présentent les mêmes limitations : en tant qu’applications natives, il faut les installer sur sa machine, et une bonne partie part du principe que l’univers informatique se résume à Windows 🙁 Par ailleurs, développer son générateur de code pour Scratch demande un investissement significatif pour entrer dans cet univers.

Google est ton ami...

... cette fois encore, mais pas avec son moteur de recherche cette fois-ci. On peut toujours les critiquer concernant leur prise de pouvoir sur le monde de l’informatique (comme on a critiqué IBM en son temps) mais il faut bien reconnaître qu’il y a quand même chez eux des gus qui ont oublié d’être bête. Et quand il s’agit de faire chauffer les neurones, que ce soit pour créer des idées nouvelles ou pour finaliser les idées que d’autres ont eues, ils savent y faire.

Une de leur créations récentes (2012 pour être précis, ce qui peut paraitre une éternité dans un monde qui bouge à la vitesse petit ’c’) est Blocky. En gros, prenez le principe de Scratch, mettez-le dans un navigateur Web sous forme de bibliothèque Javascript, rendez plus simple la création de blocs personnalisés et la génération de code, et vous aurez Blockly.

Ca peut d’ailleurs servir aussi bien à générer du code au sens habituel du terme que des données de configuration, application de plus en plus répandue, et dont un parfait exemple est l’éditeur graphique de définition de bloc, faisant partie des Block Developper Tools pour créer un DSL [3] basé sur Blockly.

Et nous y voilà : sachant que Blockly intègre out of the box un générateur de code Python parmi d’autres, quoi de plus tentant que d’y ajouter une collection de blocs pour contrôler notre bras préféré et d’intégrer le tout dans la panoplie d’applications embarquées ? Dont acte.

Blockly 4 Youpi

Partons du résultat en images :

Comment ça marche ?

Tout cela est réalisé sous forme d’une application Python indépendante, comme les autres démos embarquées dans Youpi. Elle implémente un serveur HTTP à l’aide de Bottle. Les blocs spécifiques à Youpi sont définis dans un module Javascript, en se basant sur les informations de la documentation développeur disponible sur le site Web de Blockly mentionné plus haut. J’ai choisi de tout définir en Javascript plutôt que d’utiliser JSON pour éviter de multiplier les fichiers source [4]. La toolbox fusionne des blocs standards avec ceux spécifiques à Youpi, et est définie avec le formalisme XML prévu à cet effet.

La partie cliente utilise le classique combo jQuery + Bootstrap, et s’appuie sur quelques services REST fournis par l’application Python pour le contrôle de l’exécution du programme. L’API en question est basée sur les trois points d’entrée suivants :

/run POST lance l’exécution (asynchrone) du programme. Le retour est immédiat et n’attend pas la fin de l’exécution, de manière à ne pas bloquer le client. Si le programme soumis n’est pas valide, un status d’erreur est retourné. Le programme est transmis dans le corps de la requête, sous la forme du code Python généré
/status GET retourne le status (running, terminated, error,...) actuel du programme
/abort POST interrompt l’exécution

La soumission du code Python à exécuter dans la requête peut paraître une hérésie sur le plan sécurité, mais :

  • ce code est contrôlé avant exécution, et est rejeté s’il contient des instructions spécifiques telles que import, ce qui limite très fortement les possibilités, et les restreint au final aux appels des commandes de Youpi.
  • notre serveur n’est pas en ligne et exposé à tous les agresseurs, donc pas la peine d’être paranoïaque sans raison valable.

Côté serveur, le modèle d’exécution consiste à ajouter dans le contexte d’exécution passé au built-in exec de Python un objet faisant office de proxy des commandes du bras. Les méthodes de cet objet sont des wrappers des méthodes natives de l’API de Youpi qu’on a sélectionnées pour être mise à disposition du programme exécuté. Le wrapping consiste à les encadrer par un message de log à toutes fins utiles, mais surtout par un test du signal de demande d’interruption [5]. Histoire que les choses soient moins hermétiques, voici le code du proxy en question :

class ArmProxy(object):
    PROXIED_METHODS = ('move', 'goto', 'open_gripper', 'close_gripper', 'go_home', 'move_gripper_at')
 
    def __init__(self, arm, logger, terminate_event):
        self.arm = arm
        self.logger = logger
        self._terminate_event = terminate_event

        for method_name in self.PROXIED_METHODS:
            setattr(self, method_name, self._wrap(getattr(self.arm, method_name)))
 
    def _wrap(self, method):
        def wrapper(*args, **kwargs):
            self.logger.info('.. executing %s', method.__name__)
            method(*args, **kwargs)
            if self._terminate_event.is_set():
                self.logger.info('termination signal received')
                raise Interrupted()
        return wrapper

A noter dans __init__ :

  • la construction dynamique de la collection des wrappers des méthodes natives du bras, à partir de la liste des noms de celles à supporter. Python est vraiment génial quand il s’agit de nous éviter le copier/coller et la répétition de code sans intérêt,
  • l’utilisation d’une closure dans la méthode _wrap qui retourne une fonction construite dynamiquement et basée sur une méthode passée dans son contexte (paramètre method).

Afin d’assurer le côté asynchrone et de permettre au client d’interrompre le programme en cours, son exécution est gérée par un runner qui l’active dans un thread. En voici le code :

class Runner(object):
    def __init__(self, pgm, arm, panel, logger):
        self.pgm = pgm
        self.arm = arm
        self.panel = panel
        self.logger = logger
 
        self._run_thread = None
        self._terminated_callback = None
 
    def start(self, on_terminated=None):
        if self._run_thread:
            raise RuntimeError('already running')
 
        def worker():
            self.logger.info('worker started')
            try:
                exec(self.pgm, {'arm': self.arm, 'panel': self.panel})
            except Interrupted:
                self.logger.info('worker interrupted')
            else:
                self.logger.info('worker terminated')
                if self._terminated_callback:
                    self._terminated_callback()
 
        self._terminated_callback = on_terminated
        self._run_thread = threading.Thread(target=worker)
        self._run_thread.start()
 
    @property
    def active(self):
        return self._run_thread and self._run_thread.is_alive()
 
    def join(self):
        if self._run_thread and self._run_thread.is_alive():
            self._run_thread.join(timeout=30)
            if self._terminated_callback:
                self._terminated_callback()

Conclusion

Je me suis beaucoup amusé à développer cette application, car elle aborde des domaines un peu originaux. Mais surtout parce que ça laisse entrevoir des possibilités fabuleuses basées sur les technologies mises en oeuvre. Ca va très probablement me servir dans mes activités professionnelles, et j’ai déjà quelques idées sur la question.

Pour le code intégral de cette application, rendez-vous sur son repository GitHub.


[1pendant les animations des Fêtes de la Science auxquelles il a participé avec brio

[2ou autre dispositif physique comme par exemple une carte Arduino

[3Domain Specific Language

[4j’aurais pu embarquer le JSON dans le module Javascript pour éviter cet inconvénient, mais j’ai trouvé au final la syntaxe Javascript plus lisible que la définition JSON. Affaire de goûts personnels.

[5certains appellent cela de la programmation par aspects

Un message, un commentaire ?

modération a priori

Attention, votre message n’apparaîtra qu’après avoir été relu et approuvé.

Qui êtes-vous ?

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici

Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.