P42 Dispositif à retour tactile

De Wiki d'activités IMA

Contexte du sujet

L'E-vita s'inscrit dans une nouvelle démarche en faveur de l'interface homme-machine. Depuis des année la technologie exploite nos sens de différente manière, l'idée de faire répondre la machine et de se faire comprendre par l'utilisateur, est obligatoire au bon fonctionnement de programme et autre fonctions. L'ouï, la vue sont les sens les plus exploiter par la technologie depuis le commencement. Mais depuis un long moment le toucher se répand sur les différents système afin de s'approcher au plus près de l'utilisateur, l'écran tactile s'inscrit donc dans une nouvelle ère technologique, où les machines ne sont plus contrôler via des interrupteur ou bouton, mais via les différentes possibilités du toucher sur un écran. Noter cependant que si depuis très longtemps les écran nous transmettent des informations, que l'on arrive également à contrôler des système via le son (clap des mains pour éteindre la lumière), il n'existe pas de dispositif tactile qui fonctionne en duplex (Homme vers machine, machine vers homme).

C'est dans ce contexte que le bureau d'étude de l'IRCICA met au point l'E-Vita, un dispositif tactile qui non seulement permet la communication d'un homme vers la machine mais qui est également capable de faire communiquer,grâce au toucher, la machine vers l'utilisateur. Le projet E-Vita, lancé en 2003, est capable aujourd'hui, via différentes applications test, de faire ressentir à son utilisateur différentes formes d'ondes (motifs), via des céramiques piezo-electriques. La dalle se met à vibrer sous le doigt de l'utilisateur ainsi la machine communique avec son utilisateur.

Objectif du sujet

L'objectif de ce Projet de fin d'étude, et de réétudier la loi de contrôle qui permet au µC de contrôler les céramiques. Actuellement une couche hardware supérieur récupère le mouvement du doigt ainsi que sa vitesse afin de faire ressentir à l'utilisateur un motif, une vibration électronique unique, typiquement une forme d'onde. L'idée principale et de rajouter de la dynamique dans la commande des céramiques afin que les motif ne soit plus enregistrée en dur dans le micro-controleur. Une possibilité envisageable pour cette définition de motif ainsi que la transmission de l'ordre, et de passer dans le domaine fréquentielle. Je citais plus haut que le dispositif se décompose en plusieurs couches matérielles et logicielles, afin de les faire communiquer une couche transport OSC est utilisé afin de faire communiquer le micro-controller et le Banana Pi. Seulement pour des raison évidentes lorsque la plaque vibre, il n'y a que la surface de contact qui se mets réellement à osciller, on a donc ici un soucis de delai entre le moment où l'utilisateur pose son doigt, se déplace, et le suivi de par le programme des zone de vibrations. Il est envisageable si le projet se déroule bien de pouvoir réétudier la question de la programmation événementielle de la tablette tactile, afin que se temps de latence soit le plus bas possible.

Protocole de communication OSC

Afin de faire la liaison entre le Banana Pi et le micro-controlleur, la couche liaison est réalisé grâce au protocole Open Sound Control. OSC est un protocole créer afin de faciliter la transmission d'information multimédia sur un réseau. Utilisé pour la transmission de musique cela s'adapte a notre transmission de fréquence pour la vibration des céramique. Cette communication utilise les normes TCP et UDP pour la communication temps réel entre le serveur et le client.

Les paquets sont transmis avec deux informations, la première information est la taille du paquet OCS et la deuxième le paquet lui-même. La taille des paquets OCS sont toujours un multiple de 4. Cela permet un alignement des différents block transmis. Le contenus d'un paquet peut soit être un OSC Messages ou un OSC Bundle

Quand le serveur reçoit un simple OSC Message il doit s'empresser d'invoquer la méthode associé. A la différence, le OSC Bundle qui comporte lui-même une identification de temps, la méthode devra être éxécuté si le Time-Tag du bundle est égal ou déjà passé, et à l'inverse devra stocker le bundle et lancé la méthode au bon moment si le Time-Tag indique un évèvenement futur.

Cette réalisation permet un résultat temps réel sur l'application, entre le moment du contact entre le doigt et la tablette et l'action de vibration des céramiques.

Retour des valeurs d'amplitude sur la plaque piezo

Aujourd'hui le contrôle des céramique se fait grâce à des fichiers écrits en "dur" dans le programme, chacun des ses fichiers se présente sous la forme d'un tableau de 10000 valeurs d'entier. Un entier est composé de 4 octets, sachant que 4 formes d'ondes sont utilisées dans la gamme de fréquence (50, 100, 500, 1000, 5000, 10000). Cela correspond donc à 6*4*4*10000 = 960 ko de mémoire utilisé.

Une solution évidente serait de non pas récupérer la valeur dans un tableau mais bien de la calculer voir de la prédire en fonction du déplacement, de la dimension de l'écran, de la fréquence de rafraîchissement.

Signal Sinusoîdal -> sin(wt)
Signal Carré -> x(t) = 1 , 0 < t <= T et 0, T < t < 2T.
Signal dent de scie -> x(t) = t, 0 < t < 2T
Signal Dirac

Un passage dans le domaine fréquentiel permettrait de généralisé le calcul des valeurs de retour. Ainsi qu'une représentation plus formelle du problème.


Cahier des charges

Établir une représentation visuel d'un mouvement de rotation sur une application de verrrouillage/dévérouillage par code/retour tactile.

Concevoir une librairies facilitant la programmation d'application utilisant le retour tactile.

Avancé du travail

Afin d’appréhender le problème facilement j'ai commencé par représenter un segment en direction du chiffre en cours de sélection.


Ligne numero 1.png
Ligne numero 6.png


Le code correspondant est un classique switch/case inscrit dans la fonction draw() :


  fill(255, 0, 0);
   switch (currentNumber){
   case 1:
     fill(255, 0, 0);
     line(240,340,116,800-552);
     break;
     
   case 2:
     fill(255, 0, 0);
     line(240,340,239,800-676);
     break;
   case 3:
     fill(255, 0, 0);
     line(240,340,384,800-558);
     break;
     
   case 4:
     fill(255, 0, 0);
     line(240,340,381,800-372);
     break;
 
   case 5:
  fill(255, 0, 0);
     line(240,340,237,800-257);
     break;
   case 6:
   
    fill(255, 0, 0);
     line(240,340,98,800-372);
     break;
 }

Je décide donc de réaliser un cercle autour du bouton "OK" avec 4 segments permettant d'observer une rotation du cercle toujours dans la fonction draw() :

if (!creatingCode){
     translate(240, 333); // On place le référentiel au centre du bouton "OK"
    
     rotate(direction * millis() * 0.001 * TWO_PI / 1O.0);   // Créer une rotation de 360° en 10 secondes.
     noFill();
     strokeWeight(5);
     ellipseMode(CENTER);
     ellipse(0,0,145,145); 
     line(75,0,90,0);
     line(-75,0,-90,0);
     line(0,75,0,90);
     line(0,-75,0,-90);
   }

Pour l'instant la rotation se fait sans arrêt, cela ne convient qu'a moitié, mais elle permet tout de même de tourner plus vite si jamais on appuie plus d'une fois sur une des flèches jaunes.

Afin d'obtenir une solution plus adéquate divers variables globales sont utilisées :

int direction = 0;       // Sens de direction rotation 0 = fixe / +n = décalage trigo / -n = décalage anti-trigo
int nb_frame = 0;        // Enregistre le nombre de frame lorque l'on appuie sur une des flèches.
float phi = 0.;          // Sert contient le calcul de la phase pour la rotation.

La variable direction est modifié à chaque appuie sur une des deux flèches jaunes. Elle incrémente un compteur qui reset à chaque fin de rotation. La rotation est rendu possible grâce au combo pushMatrix()/popMatrix() elle me permet d'afficher 6 segments séparés d'un angle de pi/3. La variable phi correspond à la phase du premier segment, c'est cet élémént qui réalise réellement la rotation.

if (!creatingCode){
     translate(240, 333);                                               // Centrage sur "OK"
     noFill();
     strokeWeight(5);
     ellipseMode(CENTER);
     ellipse(0,0,145,145);
     if (frameCount < nb_frame +60){                                    // Calcul de la position du premier segment
        phi = direction * ((nb_frame-frameCount)/60.) * PI/3;           // Le frameRate est fixé à 60 itérations/s
     }else{
       direction =0;                                                    // Reset de la variable d'appui
     }
     //Creation des segments
     for (int i=0; i<6; i++){                                           // Cette boucle permet donc de créer les 6 segments et de donner l'impression qu'il tourne autour du cercle 
       pushMatrix();
       rotate(phi + i*PI/3);
       line(75,0,90,0);
       popMatrix();
     }
   }

Une animation est disponible au lien suivant : Fichier:Rotation propre.mp4


Boite à outils Haut Niveau

Afin de créer un polymorphisme des classe Trectangle et Tcircle, cela permettra de créer une méthode sendControl général pour toute les classes.

Conception des classes représentant les motifs élémentaires. Conception des méthodes isIn : permet de savoir si un point de coordonnée x,y est contenu dans le motif. Conception des méthodes dessiner : permet de dessiner le motif élémentaire Première réflexions sur un la création d'un ensemble de motif élémentaire via un hashMap.

La classe abstraite Tforme

abstract class Tforme{
  String ensemble;
  int motif;
  void addMotif(int iMotif){
     motif = iMotif;
   }
  
  void sendControl(){
    println(motif);
  }
  
  abstract boolean isIn(int xpos, int ypos);
  abstract void dessiner();
  
 }

La classe Trectangle

class Trectangle extends Tforme{
 int x1, y1, x2, y2;
 
 Trectangle(int xpos, int ypos, int hauteur, int largeur){
   x1 = xpos;
   x2 = xpos + hauteur;
   y1 = ypos;
   y2 = ypos + largeur;
 }
   void dessiner(){
     rect(x1,y1,x2,y2);
   }  
   
   boolean isIn(int xpos, int ypos){
     if (xpos > x1 && xpos < x2 && ypos > y1 && ypos < y2){
       return true;
     }else{
         return false;
      }
   }
   
   void setEnsemble(String ensemble){
     this.ensemble = ensemble;
     // rajoute une entree dans la HashMap
   }
}  

La classe Tcircle

class Tcircle extends Tforme{
 int x,y;
 int radius;
  Tcircle(int xpos, int ypos, int rayon){
    x = xpos;
    y = ypos;
    radius = rayon;
  }
  void dessiner(){
    ellipseMode(CENTER);
    ellipse((float)x, (float)y, (float)radius, (float)radius);
  }
  boolean isIn(int xpos, int ypos){
    float dist = sqrt(pow(x - xpos,2) + pow(y-ypos,2));
    if (dist <= radius){
    return true;
    }else{
      return false;
    }
  }
  
  void setEnsemble(String ensemble){
    this.ensemble = ensemble;
  }
}


Fonction de Rotation

Il est facilement imaginable qu'un développeur aura besoin de manipuler ses objet dans l'espace et donc de vouloir en modifier leur angle. L'outil processing permet de réalise une rotation via la commande

rotate()

Ayant utilisé plusieurs configuration, je ne parviens pas à obtenir une rotation depuis le centre de l'élément.

Photo 1

Malgrès mes essais avec l'option RectMode() qui permet de définir un rectangle depuis son centre, je n'aboutit toujours pas.

Photo 2

Il me semble donc obligatoire d'utiliser un peu de trigonométrie :

Photo table de vérité + Représentation schématique


Boite à Outils Bas Niveau

Afin de porter le développement vers le plus de langage possible, la boite à outils contient une base faite en C. Cela permet grâce à des outils de type Java Nativ Interface de compiler la boite à outils bas niveau directement dans un langage plus adapté à la programmation graphique.

On manipule donc nos structure en C en rendant publique les méthodes :

int startEvita();    // Initialisation 
void stopEvita();     

void setTaxtTelRect(int id,int textureId, int x1, int y1, int x2, int y2);               //Définition forme géométrique où sera présente la texture : Taxtel
void setTaxtTelEllipse(int id,int textureId, int x1, int y1, int x2, int y2, int r);     //Idem mais de manière elliptique
void disableTaxtTel(int id, int textureId);                                              //Désactive le retour tactile pour la forme sélectionné


void setTextureRect(int textureId,float offset,float amplitude,float period,float ratio,char* speedFunc);     //Fonction définissant les texture possible : Signal Carré / Sinusoïde
void setTextureCos(int textureId,float offset,float amplitude,float period, char* speedFunc);

void registerCallbackPressed(void (*myfonction) (int x, int y));                  //Fonction permettant de choisir la méthode d'entrée voulu : Ecran Tactile, Souris,...
void registerCallbackDragged(void (*myfonction) (int x, int y));
void registerCallbackRelease(void (*myfonction)());
void registerCallbackSpeed(void (*myfonction) (int speedX, int speedY));
void registerCallbackTexture(void (*myfonction) (int signal, int period));

Ces différentes fonction servirons de base pour tout programme utilisant la technologie du retour tactile.


Et ci-dessous le makefile générant au choix une librairie statique/dynamique :

CC=gcc
AR=ar
EXEC=main
SOURCE=libTextureControl.c
OBJ=libTextureControl.o
STATIC=libTextureControl.a
DYNAMIC=libTextureControl.so
HEAD=libTextureControl.h


clean:
	rm -rf *.o *.a *.so *.out

dynamic:$(DYNAMIC)
	$(CC) main.c -L/home/pierre/PFEEVITA/EvitaControl/pfitouss/ -I/home/pierre/PFEEVITA/EvitaControl/pfitouss/ -lTextureControl -lm

$(DYNAMIC):$(OBJ)
	gcc -shared -o $(DYNAMIC) $(OBJ)

$(OBJ):$(SOURCE)
	gcc -c -fPIC $(SOURCE)

install:
	sudo mv $(STATIC) /usr/lib
	sudo mv $(DYNAMIC) /usr/lib
	sudo mv $(HEAD) /usr/include

static:$(STATIC)
	$(CC) main.c -L/home/pierre/PFEEVITA/EvitaControl/pfitouss/ -I/home/pierre/PFEEVITA/EvitaControl/pfitouss/ -lTextureControl -lm

$(STATIC):$(OBJ)
	$(AR) rcs $(STATIC) $(OBJ)

Application

Afin de présenter la bibliothèque, j'ai développé une application mettant en valeur le retour tactile.

Pour ceux, j'ai penser à réaliser le jeu "Memory". Le jeu se présente ainsi :

  • On dspose un nombre pair de cartes face cachée.
  • L'utilisateur en retourne une et découvre le symbole sur la face recto de la carte.
  • L'utilisateur cherche donc la paire correspondante dans le jeu mais n'a qu'un seul essai avant que les deux cartes ne se retournent face verso.
  • Grâce à sa mémoire et à la chance, il doit donc composé des paires de cartes qui se retirent du jeu quand une bonne pair est trouvé.

J'ai adapté le jeu, afin que ce ne soit pas des symboles qui s'affichent sur le recto de la carte mais bien une texture tactile. À la vue des contraintes, l'utilisateur devra validé sa paire avec un bouton adéquate.


On peut structurer le jeu ainsi :

  • Initialisation du plateau de jeu (18 cartes face verso).
    • Distribution aléatoire 18/2 textures
  • Tant que toutes les paires ne sont pas trouvées
    • Tirer la première carte
      • Sentir le motif tactile
    • Tirer la deuxième carte
      • Sentir le motif tactile
    • Valider
    • Test des motifs
      • Si texture équivalente -> Le joueur gagne 1 points, le nombre de coup joué prend +1 -> Les cartes sont retirés
      • Si texture non-équivalente -> Le nombre de coup joué prend +1 -> les cartes sont remises faces verso.
  • Fin tant que
  • Affichage du nombre de coups joués + durée de la partie.


Menu jeu.png
Game.png
Pause.png
Win.png



Les sources de la librairie et du jeu sont disponibles ici : Fichier:Source P42.zip