dimanche 19 janvier 2014

Hacking du module hid-wiimote pour fonctionner avec le Pro Controller U sous RetroPie

Introduction

Aujourd'hui, un article qui risque d'être assez long (et très probablement assez mal écrit) pour revenir sur mon activité de ces derniers jours.


Avant de commencer, le contexte : la plateforme RaspberryPi est équivalente à un téléphone portable et permet de faire de l'émulation de consoles grâce à des distributions comme RetroPie (utilisant RetroArch, un des seul émulateur pi basé sur Pico capable de faire tourner certains jeux de Megadrive, comme Sonic Spinball, et EmulsationStation pour proposer une interface de lancement des jeux). Il existe aussi Chameleon et PiMAME, mais malheureusement l'émulateur Megadrive qui y est intégré ne fonctionne pas avec tout les jeux.

J'ai pu valider cette partie, et tester avec une manette bas de gamme (moyennant quelques configurations à la main pour EmulationStation et RetroArch).
Maintenant il peut être intéressant de pouvoir jouer à 2, et donc connecter 2 manettes, mais :
  • les fils, c'est chiants
  • la Pi n'aime pas trop se faire tirer du courant, ça la fait planter

D'où cette idée : acheter un dongle bluetooth et utiliser des manettes sans fil. J'en ai commandé 2 sur Amazon : 

Cette première n'est absolument pas Bluetooth contrairement à ce qu'indique son nom .. Il faut utiliser leur gros dongle .. Amazon va la récupérer.
L'autre manette est celle ci :
Qui n'est pas une manette WiiU Pro contrairement à ce qu'indique son nom, mais une Wii Classic Controller Pro (qui fait aussi Wiimote simple).
Elle fonctionne en bluetooth comme les manettes Wii, on peut donc en associer plusieurs sur un simple dongle bluetooth.

Première approche

Ma première approche a été d'utiliser les tutos disponibles sur internet, tel que :
Ils se basent sur cwiid couplé avec wminput pour gérer les boutons. Seulement .. avec cette manette, ça ne fonctionne pas, il y a des messages d'erreur ("Received unexpected write report") et la partie Classic (il y a un switch sur la manette) fait comme si aucune touche n'était pressée. J'ai tenté de compiler la derniere version de cwiid, qui m'a permit de jouer avec les leds, mais pas plus.

J'ai fait d'autres recherche et suis tombé sur quelques liens : 
Il a fallu compiler wiiuse (je passe les détails, c'est expliqué), mais je n'ai pas eu beaucoup plus de succès.

J'ai songé à un moment à parser les messages de la manette à la main, en prenant la méthode suitante : http://smus.com/prototyping-wii-remote-python/ mais quand un rapide test montre que la manette balance des 0xFF en continu, c'est qu'il faut lui envoyer les bonnes données avant, et bon .. flemme.

L'approche xwiimote / hid-wiimote

C'est en faisant des recherche sur la WiiU Pro Controller que j'ai vu qu'il existait une manette pour WiiU, qui n'est donc pas la même, et qu'il existe des logiciels pour l'utiliser (mais la mienne n'est pas celle-ci, même si je l'ai cru à un moment, le nom de la manette prêtant à confusion) : 
XWiimote is an open-source linux device driver for Nintendo Wii / Wii U Remotes and compatible devices. It is a relatively new driver that tries to supercede cwiid, wiiuse and others by integrating the driver into existing linux infrastructure. The project consists of an official linux kernel driver, which is part of the kernel since linux-3.1, an extension to bluez, the official linux bluetooth stack, an X11 input driver, some user-space helpers and test applications.
Surprise donc ! Il existe un module kernel, qui se prétend mieux que cwiid et wiiuse, et qui se trouve être présent dans RetroPie (kernel 3.6.11+). Il y a même un super programme qui affiche les touches de la Wiimote et ses extensions en ASCII (xwiishow) !
(tiré de http://leftbraintinkering.blogspot.fr/2014/01/wii-remote-plus-and-linux.html)
Mais après compilation de xwiishow, malheureusement même constat, le Classic controller ne fonctionne pas :(. Je commence à penser que la manette est défectueuse.
A noter qu'il faut utiliser hidd pour associer la Wiimote, bluez demandant un PIN.

Hack de hid-wiimote

Ayant jeté un œil aux sources du module hid-wiimote, et n'ayant plus rien à perdre (a part renvoyer la manette, mais elle me plait vraiment avec sa forme à la SuperNES), je me lance dans la modification de ce module.
Il faut pour ça pouvoir compiler, sur une Pi contrairement aux petits logiciels d'avant, c'est impensable, il faut donc passer par de la cross-compilation. l'avantage avec la Pi c'est qu'il existe plein de docs/tutos, on trouve donc des instructions pour cela : http://elinux.org/RPi_Kernel_Compilation.
Une VM Ubuntu x64, les libs pour exécuter des programmes x86, puis il suffit de suivre les instructions :

KERNEL_SRC=/home/user/raspbian-kernel/linux-rpi-3.6.y/
CCPREFIX=/home/user/raspbian-kernel/tools-master/arm-bcm2708/arm-bcm2708hardfp-linux-gnueabi/bin/arm-bcm2708hardfp-linux-gnueabi-
make mrproper
make ARCH=arm CROSS_COMPILE=${CCPREFIX} oldconfig
make ARCH=arm CROSS_COMPILE=${CCPREFIX}
make ARCH=arm CROSS_COMPILE=${CCPREFIX} modules

A noter qu'il faut compiler le kernel (make) avant de compiler les modules (make modules), sinon :
ERROR: could not insert 'hid_wiimote': Exec format error

Reconnaissance du Classic Controller

Le principal problème étant que l'extensions Classic n'est pas reconnu, alors qu'a priori présente, je décide de la forcer :
  wmem = 0x55;
  wiimote_cmd_write(ext->wdata, 0xa400f0, &wmem, sizeof(wmem));
  wmem = 0x0;
  wiimote_cmd_write(ext->wdata, 0xa400fb, &wmem, sizeof(wmem));
  return WIIEXT_CLASSIC;
Et la, même si rien n'est reconnu ou fonctionnel dans xwiishow, evtest affiche bien des boutons en mode Classic !
Mais le stick gauche ne fonctionne pas en vertical et les triggers font 2 actions au lieu d'une.

Debug des controles

Pour voir ce qu'envoi la manette, j'essaye un débogage (comme je peux, le debugging de kernel je connais pas), c'est sale (mais fonctionnel) :
#define BYTETOBINARYPATTERN "%d%d%d%d%d%d%d%d"
#define BYTETOBINARY(byte)  \
  (byte & 0x80 ? 1 : 0), \
  (byte & 0x40 ? 1 : 0), \
  (byte & 0x20 ? 1 : 0), \
  (byte & 0x10 ? 1 : 0), \
  (byte & 0x08 ? 1 : 0), \
  (byte & 0x04 ? 1 : 0), \
  (byte & 0x02 ? 1 : 0), \
  (byte & 0x01 ? 1 : 0) 
  
  
  printk(KERN_INFO "Byte %d "BYTETOBINARYPATTERN"", 0, BYTETOBINARY(payload[0]));
  printk(KERN_INFO "Byte %d "BYTETOBINARYPATTERN"", 1, BYTETOBINARY(payload[1]));
  printk(KERN_INFO "Byte %d "BYTETOBINARYPATTERN"", 2, BYTETOBINARY(payload[2]));
  printk(KERN_INFO "Byte %d "BYTETOBINARYPATTERN"", 3, BYTETOBINARY(payload[3]));
  printk(KERN_INFO "Byte %d "BYTETOBINARYPATTERN"", 4, BYTETOBINARY(payload[4]));
  printk(KERN_INFO "Byte %d "BYTETOBINARYPATTERN"", 5, BYTETOBINARY(payload[5]));
Et ça donne pleiiiiin de lignes dans les logs :
Jan 17 17:16:52 raspberrypi kernel: [65820.154165] Byte 1 00100000Byte 2 00010000
Jan 17 17:16:52 raspberrypi kernel: [65820.154198] Byte 3 00000000Byte 4 11111111
Jan 17 17:16:52 raspberrypi kernel: [65820.154221] Byte 5 11111111Byte 0 10100000
Jan 17 17:16:52 raspberrypi kernel: [65820.161071] Byte 1 00100000Byte 2 00010000
Jan 17 17:16:52 raspberrypi kernel: [65820.161100] Byte 3 00000000Byte 4 11111111
Jan 17 17:16:52 raspberrypi kernel: [65820.161121] Byte 5 11111111Byte 0 10100000
Comme je disais, c'est sale, mais ça fonctionne et permet de repérer doucement les touches, je me suis alors construit une carte :
Byte 0 10100000
       ^^-------RX(12)
         ^^^^^^-LX

Byte 1 00100000
       ^^-------RX(34)
         ^^^^^^-LY

Byte 2 00010000
       ^--------RX(5)
        ^^------TL
          ^^^^^-RY


Byte 3 00000000
       ^^^------TL
          ^^^^^-TR

Byte 4 11111111
       ^--------RIGHT
        ^-------DOWN
         ^------BTL
          ^-----MINUS
           ^----HOME
            ^---PLUS
             ^--BTR
              ^-1

Byte 5 11111111
       ^--------TL2
        ^-------B = ZL
         ^------Y
          ^-----A
           ^----X = ZR
            ^---TR2
             ^--LEFT
              ^-UP

Corrections

Il s'avère que cela correspond aux indications dans les sources de hid-wiimote .. C'est donc ailleurs que se situe le problème. Entre temps je tombe sur les sources du module pour le kernel 3.11, qui sont corrigées
                lx = payload[0] & 0x3e;
                ly = payload[0] & 0x3e;
lx = ly ? Oui, il y a comme un problème.
Il y a aussi une double négation sur les touches qui fait qu'elle apparaissent comme pressées en permanence, c'est corrigé aussi.

J'en profite aussi pour modifier les touches du D-Pad pour mapper des boutons au lieu des touches UP/DOWN/LEFT/RIGHT, qui ne sont pas reconnues par retroarch-joyconfig.
Enfin il y a un dernier bug qui est apparu quand j'ai voulu tester l'utilisation avec 2 wiimotes (voir si les leds sont gérées .. la réponse est non), qui nécessite de supprimer l'intégration du report des informations batterie dans le kernel, sinon il tente de créer 2 fois le même périphérique, et alors :
[178790.876742] ------------[ cut here ]------------
[178790.876794] WARNING: at fs/sysfs/dir.c:536 sysfs_add_one+0x88/0xc0()
[178790.876810] sysfs: cannot create duplicate filename '/class/power_supply/wiimote_battery'
[...]
[178790.883777] ---[ end trace 66509376675ee1a0 ]---
[178791.878245] power_supply wiimote_battery: driver failed to report `capacity' property: 4294967291
[178791.878349] wiimote 0005:057E:0306.0015: Cannot register battery device
[178792.882087] wiimote: probe of 0005:057E:0306.0015 failed with error -17

Bref, l'ensemble des modification est visible ici :
https://github.com/Alex131089/raspberrypi-linux/compare/raspberrypi:rpi-3.6.y...rpi-3.6.y-hid-wiimote

Conclusion

Bien que je ne soit pas entré dans le détail, j'ai une petite vue du fonctionnement des Wiimote, et de la gestion au niveau du kernel. Première expérience de cross-compilation aussi, merci au projet RaspberryPi qui a préparé les outils.

Ma manette est donc fonctionnelle, il reste à tester en condition réelle, mais les contrôles semblent tous reconnus cette fois. Il reste à créer un script qui pourra scanner une liste de commande (hidd --connect) ou toute commande (hidd --search) pour s'y connecter, qui scannera en permanence, et qui affectera les bonnes leds (p1, p2) aux commandes, à coup de recherche dans /sys/class ^^

Je joins aussi mon fichier de "travail" qui contient moulte liens sur lesquels je suis tombé pendant mes recherches.