Yocto-Display: les animations autonomes

Yocto-Display: les animations autonomes

On vient de réaliser qu'on a jamais vraiment parlé du système d'animations intégré aux écrans Yoctopuce. La documentation mentionne son existence, les fonctions nécessaires sont documentées, il y a un exemple dans chaque libraire de programmation, mais on n'a jamais expliqué en détail comment ça marche. Cette semaine, après 7 ans, on répare cet oubli.


Les animations

L'idée derrière le système d'animations des écrans Yoctopuce consiste à enregistrer une séquence d'actions effectuées sur l'écran, sauver le résultat dans le filesystem de l'écran pour pouvoir ensuite le rejouer à volonté. Le gros intérêt, c'est que ces animations sont exécutées par l'écran en toute autonomie. Une fois lancée par l'ordinateur de contrôle, ce dernier n'a plus à intervenir pour contrôler leur exécution.

Un exemple

Pour illustrer le concept on va utiliser une version simplifiée de l'exemple de programmation qu'on trouve dans toutes les librairies Yoctopuce et qui illustre le concept de double-buffering: Il dessine un flocon de Koch qui tourne.

Le flocon de Koch, passage obligé pour tout étudiant en informatique
Le flocon de Koch, passage obligé pour tout étudiant en informatique


On a choisi ce motif récursif pour illustrer cet article parce qu'on peut très facilement augmenter la complexité du dessin, on aura l'occasion d'essayer.


profondeur=0    profondeur=1
profondeur=2    profondeur=3
Le flocon de Koch à différents niveau de récursion



Voici le code qui dessine et fait tourner le flocon depuis l'ordinateur de contrôle:

import os, sys, math
from yocto_api import *
from yocto_display import *

# this is the recusive function to draw 1/3nd of the Von Koch flake
def recursiveLine(layer, x0, y0, x1, y1, deep):
    if deep <= 0:
        layer.moveTo(int(x0 + 0.5), int(y0 + 0.5))
        layer.lineTo(int(x1 + 0.5), int(y1 + 0.5))
    else:
        dx = (x1 - x0) / 3
        dy = (y1 - y0) / 3
        mx = ((x0 + x1) / 2) + (0.87 * (y1 - y0) / 3)
        my = ((y0 + y1) / 2) - (0.87 * (x1 - x0) / 3)
        recursiveLine(layer, x0, y0, x0 + dx, y0 + dy, deep - 1)
        recursiveLine(layer, x0 + dx, y0 + dy, mx, my, deep - 1)
        recursiveLine(layer, mx, my, x1 - dx, y1 - dy, deep - 1)
        recursiveLine(layer, x1 - dx, y1 - dy, x1, y1, deep - 1)

# Setup the API to use local USB devices
errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
    sys.exit("init error" + errmsg.value)

disp = YDisplay.FirstDisplay()
if disp is None:
    sys.exit('No display connected')

# display clean up
disp.resetAll()
l1       = disp.get_displayLayer(1)
l2       = disp.get_displayLayer(2)
centerX  = disp.get_displayWidth() / 2
centerY  = disp.get_displayHeight() / 2
radius   = disp.get_displayHeight() / 2
a = 0
framesCount =15
l1.hide()  # double buffering L1 is hidden, l2 stays visible
while True:
    # we draw in the hidden layer
    l1.clear()
    # change the snowflake angle
    a = a + (2 * math.pi) / (3 * framesCount)
    # Draws 3 thirds of the snowflake
    for i in range(0, 3):
        recursiveLine(l1, centerX + radius * math.cos(a + i * 2.094),
                      centerY + radius * math.sin(a + i * 2.094),
                      centerX + radius * math.cos(a + (i + 1) * 2.094),
                      centerY + radius * math.sin(a + (i + 1) * 2.094), 2)
    # then we swap contents with the visible layer
    disp.swapLayerContent(1, 2)



Si vous lancez ce code, vous obtiendrez l'animation suivante:

L'animation générée par ce code.
L'animation générée par ce code.


Mais dès que vous stoppez le programme, l'animation s'arrête. Pour transformer ce code afin qu'il construise une animation autonome, on aura besoin de quelques appels:

  • Ydisplay.newSequence(): Pour commencer l'enregistrement de l'animation
  • Ydisplay.pauseSequence(): Pour introduire un temps d'attente dans l'exécution de la séquence
  • Ydisplay.saveSequence(): Pour arrêter l'enregistrement et sauver l'animation dans le modules
  • Ydisplay.playSequence(): Pour lancer l'exécution d'une séquence pré-enregistrée. Notez que qu'on peut utiliser playSequence à l'intérieur d'une séquence.
  • Ydisplay.stopSequence(): Pour, au besoin, arrêter l'exécution d'une séquence

Le minimum syndical consiste à ajouter newSequence() au début du code et disp.saveSequence() à la fin du code. On a aussi remplacé la boucle infinie par une boucle "for" qui dessine l'animation sur un tiers de révolution. En effet, la géométrie de base du flocon étant un triangle équilatéral, une révolution complète peut être divisée en trois parties strictement identiques, il suffit donc de n'en dessiner qu'une.

...
a = 0
framesCount =15
#start recording
disp.newSequence()
l1.hide()  # double buffering L1 is hidden, l2 stays visible
for j in range(0,framesCount) :
    # we draw in the hidden layer
    l1.clear()
    # change the snowflake angle
    a = j*  (2 * math.pi) / (3 * framesCount)
    # Draws 3 thirds of the snowflake
    for i in range(0, 3):
        recursiveLine(l1, centerX + radius * math.cos(a + i * 2.094),
                      centerY + radius * math.sin(a + i * 2.094),
                      centerX + radius * math.cos(a + (i + 1) * 2.094),
                      centerY + radius * math.sin(a + (i + 1) * 2.094), 2)
    # then we swap contents with the visible layer
    disp.swapLayerContent(1, 2)
#stop recording and save
disp.saveSequence("vonkoch.seq")


Notez que pendant un enregistrement les appels aux fonctions graphiques de l'écran sont enregistrées mais pas affichées sur l'écran, l'exécution de ce code n'a donc aucun effet sur l'affichage. Par contre, en utilisant le Virtualhub, on peut constater qu'un fichier "vonkoch.seq" est apparu dans le file system du module.

L'animation est enregistrée dans le filesystem
L'animation est enregistrée dans le filesystem


On peut utiliser le VirtualHub pour lancer l'animation on obtient le résultat suivant:

L'animation générée par ce code.
L'animation générée par ce code.


L'animation fait tourner le flocon sur un tiers de tour à toute allure, beaucoup trop vite en fait. On peut améliorer notre code de génération en insérant une temporisation de 50ms et forcer une boucle infinie ajoutant l'appel disp.playSequence("vonkoch.seq") juste avant disp.saveSequence("vonkoch.seq"). A ce moment, le module est encore en train d'enregistrer, il va donc enregistrer qu'il doit lancer la séquence vonkoch.seq à la fin de l'animation, ce qui cause une boucle infinie dans l'animation. Pendant qu'on y est, on ajoute un deuxième playSequence() à la fin du code pour lancer immédiatement la séquence et éviter d'avoir à le manuellement faire avec le VirtualHub. Il est important de comprendre que comme les autres fonctions graphiques, playSequence ne se comporte pas de la même façon suivant qu'on soit en mode enregistrement ou non.

 
...
a = 0
framesCount =15
#start recording
disp.newSequence()
l1.hide()  # double buffering L1 is hidden, l2 stays visible
for j in range(0,framesCount) :
    # we draw in the hidden layer
    l1.clear()
    # change the snowflake  angle
    a = j*  (2 * math.pi) / (3 * framesCount)
    # Draws 3 thirds of the snowflake
    for i in range(0, 3):
        recursiveLine(l1, centerX + radius * math.cos(a + i * 2.094),
                      centerY + radius * math.sin(a + i * 2.094),
                      centerX + radius * math.cos(a + (i + 1) * 2.094),
                      centerY + radius * math.sin(a + (i + 1) * 2.094), 2)
    # then we swap contents with the visible layer
    disp.swapLayerContent(1, 2)
    # pause playback for 50ms
    disp.pauseSequence(50)
#creates a endless loop
disp.playSequence("vonkoch.seq")
#stop recording and save
disp.saveSequence("vonkoch.seq")
#launch the animation
disp.playSequence("vonkoch.seq")


L'animation finale, identique à la version pilotée par l'API
L'animation finale, identique à la version pilotée par l'API



En augmentant le dernier paramètre de l'appel à recursiveLine, on augmente la profondeur de récursion et donc la complexité du dessin, ce qui permet de mettre en évidence que les animations autonomes sont exécutée beaucoup plus rapidement que leur version pilotées par l'API. Le volume de communication augmente exponentiellement avec la profondeur. Avec une profondeur à 3 la version API commence à peiner pour suivre le rythme alors qu'il reste indispensable de mettre un pauseSequence() dans la version animation.

La version autonome ralenti moins quand on augmente la profondeur
La version autonome ralenti moins quand on augmente la profondeur


Limitations

Les animations autonomes ont malgré tout quelques limitations qu'il est bon de connaître:

  • Par définition, elles sont pré-calculées vous ne pouvez pas les changer dynamiquement. Vous pourriez être tenté de les créer à la volée, mais n'oubliez pas que le nombre de cycles d'écriture de la mémoire flash du module est limité.
  • Vous pouvez les utiliser pour afficher une image que vous auriez préalablement sauvé dans le filesystem du module (displaylayer.drawImage()), par contre vous le pouvez pas afficher un blob binaire (displaylayer.drawBitmap()).
  • Contrairement aux autres écrans, le filesystem des Yocto-MiniDisplay n'est pas persistant: les animations sauvées sont oubliées dès la mise hors-tension.

Applications

L'application typique des animations autonomes est le splash-screen à l'allumage de l'écran, la petite animation Yoctopuce à l'allumage de l'écran est réalisée de cette manière et vous pouvez même utiliser le VirtualHub pour la remplacer par la votre.

En fait, ça, c'est juste une animation pré-enregistrée sur le module
En fait, ça, c'est juste une animation pré-enregistrée sur le module


Mais ce n'est pas la seule application: pendant qu'une animation tourne, l'écran reste parfaitement fonctionnel, vous pouvez continuer à afficher des trucs dans les couches qui ne sont pas affectées par l'animation. Rien de vous empêche donc de "sous traiter" une partie de l'affichage pour aller plus vite.

Commenter aucun commentaire Retour au blog












Yoctopuce, get your stuff connected.