Interfaçage de boutons avec MacOSX via un arduino

Un jeu TRON, c’est bien. Avec des manettes, c’est mieux. Et quand on le fait soi-même avec des technologies imposées et un temps très limité, c’est encore meilleur ! Comme...

Un jeu TRON, c’est bien. Avec des manettes, c’est mieux. Et quand on le fait soi-même avec des technologies imposées et un temps très limité, c’est encore meilleur !

Comme certains d’entre vous l’ont probablement observé lors des journées portes ouvertes/journées de liste/journées concours/journées où on n’avait rien d’autre à faire, nous avons interfacé le jeu lightcycle dans TRON ( Armagettron ) avec des « manettes » faites maison (une plaque de prototypage et deux boutons, quoi) et avec mon ordi, un MacBookPro 3,1 qui en l’occurence tournait sous Mac OS 10.6 (ma Gentoo n’a pas encore de serveur X installé et configuré et la réalisation devait se faire en environ 2 heures…).

Cet article présentera une méthode pour procéder à cet interfaçage. Probablement pas la meilleure, ou la plus optimisée, mais c’est une méthode qui fonctionne.

Tout d’abord, nous aurons besoin :

  • De fils électriques et de la pince à dénuder qui va avec (voire vos dents, si vous êtes Victor)
  • De trois cartes de prototypage (deux pour les manettes, une pour le support)
  • D’un arduino (dans notre cas, un arduino Uno)
  • De quatre boutons poussoirs (deux par manette)
  • De quatre résistances de 2.2KΩ
  • De vos mimines

Commencez par réaliser le montage suivant relativement simple :

Montage électronique tron

Le montage d'une "manette" de tron

 

Vous observerez qu’il n’y a qu’une seule manette de faite. Il est probable que vous arriviez vous-même à faire la seconde sur le modèle de la première.

Branchez cette seconde manette sur les pins 4 et 5 de l’arduino. On évitera d’utiliser les pins 0 et 1 dans la mesure où celles-ci servent à la communication série que nous verrons après.

Ce montage permet, simplement, de brancher deux boutons sur l’arduino et de faire en sorte que celui-ci capte du « 0″ lorsque les boutons ne sont pas appuyés et du « 1″ lorsqu’ils le sont.

Nous allons à présent faire un code arduino simple pour envoyer des caractères ascii par le port série à l’ordinateur.

int prev2, prev3, prev4, prev5 = HIGH;
int cur2, cur3, cur4, cur5 = LOW;
char buff[5] = {0};
int i = 0;

void setup() {
  pinMode(13, OUTPUT);

  for (int i = 2 ; i <= 5 ; i++){
    pinMode(i, INPUT);
  }

  Serial.begin(9600);

}

void loop() {
  i = 0;
  prev2 = cur2;
  cur2 = digitalRead(2);
  if ((cur2 == HIGH) && (prev2 == LOW)){
    buff[i] = 'a';
    i += 1;
  }

  prev3 = cur3;
  cur3 = digitalRead(3);
  if ((cur3 == HIGH) && (prev3 == LOW)){
    buff[i] = 'z';
    i += 1;
  }

  prev4 = cur4;
  cur4= digitalRead(4);
  if ((cur4 == HIGH) && (prev4 == LOW)){
    buff[i] = 'e';
    i += 1;
  }

  prev5 = cur5;
  cur5 = digitalRead(5);
  if ((cur5 == HIGH) && (prev5 == LOW)){
    buff[i] = 'r';
    i += 1;
  }

  if (i != 0) {
    buff[i] = '\0';
    Serial.print(buff);
  }
  delay(10);
}

Ce code est relativement simple et envoie simplement sur le port série une lettre (‘a’, ‘z’, ‘e’ ou ‘r’) selon le bouton pressé.

Attaquons maintenant la partie AppleScript. Je précise qu’il est normalement simple sous linux d’utiliser la Xlib ou autre pour simuler des entrées claviers. Toutefois, ayant 45 minutes restantes et n’ayant toujours pas envie de compiler de serveur X, nous nous en passerons !
Notre algo est simple : lire le port série, envoyer ça comme si on tapait au clavier… le tout en boucle.

Pour lire le port série, même si une commande shell ou une lecture de fichier fonctionne impeccablement, je préfère utiliser la bibliothèque applescript SerialPortX. C’est propre, ça fonctionne pas mal, et ça plante quand on tente de lire un truc qui n’existe pas.

Pour envoyer des données comme si elle étaient tapées au clavier, on utilisera le bout de code suivant :

to sendToKey(k)
  tell application "System Events"
    key code k
  end tell
end sendToKey

Cette fonction enverra le code « k » voulu comme s’il était entré au clavier. Seul problème : comment connaître le bon code pour chaque lettre ? C’est là qu’intervient l’application Full Key Codes. Cette dernière va vous fournir tout ce qui est nécessaire.

Je crois qu’on a tout :

Voilà un applescript naïf qui fonctionne :

set portList to serialport list
if portList contains "/dev/cu.usbmodem1d11" then
  set portNum to serialport open "/dev/cu.usbmodem1d11"
else if portList contains "/dev/cu.usbmodem1a21" then
  set portNum to serialport open "/dev/cu.usbmodem1a21"
else
  beep
  display dialog "Branche l'arduino, naab ! " buttons {"Quit"}
  return
end if

to sendToKey(k)
  tell application "System Events"
    key code k
  end tell
end sendToKey

tell application "Armagetron Advanced"
  activate
end tell

repeat
  set portChat to serialport read portNum for 1

  set sendSomething to false
  set toSend to "i"
  if portChat contains "a" then
    sendToKey(12)
  end if

  if portChat contains "z" then
    sendToKey(13)
  end if

  if portChat contains "e" then
    sendToKey(14)
  end if

  if portChat contains "r" then
    sendToKey(15)
  end if

-- delay 0.1
end repeat

serialport close portNum

Voilà. Oui le code pourrait être très facilement mieux, mais autant ne pas tout vous filer tout cuit…

Si vous testez un peu le tout, vous observerez un bug récurrent : si un des deux joueurs s’acharne sur sa manette, le second ne pourra pas du tout jouer ou placer une action…

Ce bug est en fait dû à la bibliothèque « Serial » arduino qui gère tout ça et qui ajoute un délai de beaucoup entre deux utilisation du port série… Heureusement, il existe un truc qui marche (testé et approuvé par moi si vous utilisez le port série pour plein de petits messages et pas pour du gros message).

Nous voici donc avec un jeu fonctionnel et prêt à vous éclater en JPO et autres événements.