Différences
Ci-dessous, les différences entre deux révisions de la page.
| Les deux révisions précédentes Révision précédente Prochaine révision | Révision précédente | ||
|
materiel:gametrak:start [2025/10/31 20:46] emoc [Ressources] |
materiel:gametrak:start [2025/10/31 21:39] (Version actuelle) emoc [Tentative d'utilisation] |
||
|---|---|---|---|
| Ligne 13: | Ligne 13: | ||
| ===== Tentative d'utilisation ===== | ===== Tentative d'utilisation ===== | ||
| - | En le branchant sur un odi, il est reconnu par ''lsusb'' | + | En le branchant sur un ordi, il est reconnu par ''lsusb'' |
| Bus 002 Device 015: ID 14b7:0982 In2Games Ltd. Game-Trak V1.3 | Bus 002 Device 015: ID 14b7:0982 In2Games Ltd. Game-Trak V1.3 | ||
| Bus 002 Device 014: ID 0f30:001c Jess Technology Co., Ltd PS3 Guitar Controller Dongle | Bus 002 Device 014: ID 0f30:001c Jess Technology Co., Ltd PS3 Guitar Controller Dongle | ||
| Ligne 39: | Ligne 39: | ||
| Mais impossible de récupérer les données HiD, les tests avec ChucK ou un script python adapté n'ont rien donné. Les formats de communication utilisés sur PS2 pour le USB-HID ne sont pas les mêmes que sur PC, il ne suffit pas d'avoir un périphérique USB pour pouvoir le brancher sur un PC ou vice versa, triste nouvelle! | Mais impossible de récupérer les données HiD, les tests avec ChucK ou un script python adapté n'ont rien donné. Les formats de communication utilisés sur PS2 pour le USB-HID ne sont pas les mêmes que sur PC, il ne suffit pas d'avoir un périphérique USB pour pouvoir le brancher sur un PC ou vice versa, triste nouvelle! | ||
| - | En me renseignant un peu plus sur les gametrak, je note qu'il y a plusieurs versions | + | **evtest** permet aussi de voir les events liés au périphériques d'entrée ( https://manned.org/evtest ) |
| + | sudo evtest | ||
| + | |||
| + | ==== Évènements bruts HID (hidraw) ==== | ||
| + | |||
| + | Voici un script python3 pour retrouver les valeurs brutes HID (merci les LLM), à démarrer avec ''sudo python3 hidraw_reader.py --device /dev/hidraw2'' à adapter en fonction de ce que qu'a relevé dmesg ci-dessus (pour les options en ligne de commande, voir commentaires du script) | ||
| + | <accordion> | ||
| + | <panel title="hidraw_reader.py (cliquer pour afficher le code)"> | ||
| + | <code python hidraw_reader.py> | ||
| + | #!/usr/bin/env python3 | ||
| + | """ | ||
| + | hidraw_reader.py - lire les reports HID bruts depuis /dev/hidrawN et afficher hex + timestamp | ||
| + | Usage examples: | ||
| + | # lister les hidraw disponibles | ||
| + | python3 hidraw_reader.py --list | ||
| + | # lire un device précis | ||
| + | python3 hidraw_reader.py --device /dev/hidraw2 | ||
| + | # lire en spécifiant vendor:product (hex, ex: 046d:c52b) | ||
| + | python3 hidraw_reader.py --vidpid 046d:c52b | ||
| + | # écrire la sortie dans un fichier | ||
| + | python3 hidraw_reader.py --device /dev/hidraw2 --output dump.log | ||
| + | |||
| + | Fonctions: | ||
| + | - détection simple des attributs USB via /sys (idVendor,idProduct,manufacturer,product) | ||
| + | - lecture non-bloquante avec select, affichage hex + ascii + timestamp | ||
| + | - gestion propre des droits (tester en root si permission refusée) | ||
| + | """ | ||
| + | import os, sys, time, select, binascii | ||
| + | |||
| + | def find_hidraw_devices(): | ||
| + | """Retourne liste de dicts: {'path':'/dev/hidraw0','hid':'hidraw0','vid':'046d', 'pid':'c52b', 'manufacturer':..., 'product':...}""" | ||
| + | res = [] | ||
| + | devs = sorted([d for d in os.listdir('/dev') if d.startswith('hidraw')]) | ||
| + | for d in devs: | ||
| + | path = os.path.join('/dev', d) | ||
| + | info = {'path': path, 'hid': d, 'vid': None, 'pid': None, 'manufacturer': None, 'product': None} | ||
| + | # sys path e.g. /sys/class/hidraw/hidraw0/device -> may be a symlink to ../../../.../usbX/X-X:1.X/... | ||
| + | try: | ||
| + | sysnode = os.path.join('/sys/class/hidraw', d, 'device') | ||
| + | if os.path.exists(sysnode): | ||
| + | # climb up parents to find idVendor/idProduct (up to 6 levels) | ||
| + | p = os.path.realpath(sysnode) | ||
| + | for _ in range(6): | ||
| + | vendor_file = os.path.join(p, 'idVendor') | ||
| + | product_file = os.path.join(p, 'idProduct') | ||
| + | if os.path.exists(vendor_file) and os.path.exists(product_file): | ||
| + | with open(vendor_file,'r') as f: info['vid'] = f.read().strip() | ||
| + | with open(product_file,'r') as f: info['pid'] = f.read().strip() | ||
| + | # optional fields | ||
| + | man = os.path.join(p, 'manufacturer') | ||
| + | prod = os.path.join(p, 'product') | ||
| + | if os.path.exists(man): | ||
| + | try: | ||
| + | with open(man,'r') as f: info['manufacturer'] = f.read().strip() | ||
| + | except: | ||
| + | pass | ||
| + | if os.path.exists(prod): | ||
| + | try: | ||
| + | with open(prod,'r') as f: info['product'] = f.read().strip() | ||
| + | except: | ||
| + | pass | ||
| + | break | ||
| + | # climb up | ||
| + | parent = os.path.dirname(p) | ||
| + | if parent == p: | ||
| + | break | ||
| + | p = parent | ||
| + | except Exception as e: | ||
| + | pass | ||
| + | res.append(info) | ||
| + | return res | ||
| + | |||
| + | def hexdump_line(data, base_offset=0): | ||
| + | hexpart = ' '.join(f"{b:02x}" for b in data) | ||
| + | asciipart = ''.join((chr(b) if 32 <= b < 127 else '.') for b in data) | ||
| + | return f"{base_offset:08x} {hexpart:<48} |{asciipart}|" | ||
| + | |||
| + | def print_report(ts, bts, out): | ||
| + | # print timestamp and hexdump-like line(s) | ||
| + | header = f"[{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(ts))}.{int((ts - int(ts))*1000):03d}] len={len(bts)}" | ||
| + | print(header, file=out) | ||
| + | # chunk into 16 bytes per line | ||
| + | for i in range(0, len(bts), 16): | ||
| + | chunk = bts[i:i+16] | ||
| + | print(hexdump_line(chunk, i), file=out) | ||
| + | out.flush() | ||
| + | |||
| + | def open_nonblocking(path): | ||
| + | import fcntl, os, stat | ||
| + | fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK) | ||
| + | # make file descriptor a file object for read() | ||
| + | return os.fdopen(fd, 'rb', buffering=0) | ||
| + | |||
| + | def main(): | ||
| + | import argparse, sys, time | ||
| + | parser = argparse.ArgumentParser(description="hidraw reader - affichage des reports HID bruts") | ||
| + | parser.add_argument('--list', action='store_true', help='Lister les /dev/hidraw* détectés') | ||
| + | parser.add_argument('--device', '-d', help='Chemin vers /dev/hidrawN (ex: /dev/hidraw2)') | ||
| + | parser.add_argument('--vidpid', help='Filtrer par vendor:product hex (ex: 046d:c52b)') | ||
| + | parser.add_argument('--interval', type=float, default=0.2, help='Timeout select en secondes (default 0.2)') | ||
| + | parser.add_argument('--output', '-o', help='Chemin fichier pour écrire la sortie (stdout si non fourni)') | ||
| + | args = parser.parse_args() | ||
| + | |||
| + | devs = find_hidraw_devices() | ||
| + | if args.list: | ||
| + | if not devs: | ||
| + | print("Aucun /dev/hidraw trouvé.") | ||
| + | return 0 | ||
| + | print("Detected hidraw devices:") | ||
| + | for d in devs: | ||
| + | print(f" {d['path']} vid:pid={d.get('vid') or '----'}:{d.get('pid') or '----'} manufacturer={d.get('manufacturer') or ''} product={d.get('product') or ''}") | ||
| + | return 0 | ||
| + | |||
| + | target = None | ||
| + | if args.device: | ||
| + | target = args.device | ||
| + | elif args.vidpid: | ||
| + | v,p = args.vidpid.split(':') | ||
| + | for d in devs: | ||
| + | if d.get('vid') and d.get('pid') and d['vid'].lower() == v.lower() and d['pid'].lower() == p.lower(): | ||
| + | target = d['path'] | ||
| + | break | ||
| + | if not target: | ||
| + | print(f"Pas de périphérique hidraw correspondant à {args.vidpid}", file=sys.stderr) | ||
| + | return 2 | ||
| + | else: | ||
| + | # if only one device, choose it | ||
| + | if len(devs) == 1: | ||
| + | target = devs[0]['path'] | ||
| + | else: | ||
| + | print("Plusieurs périphériques hidraw détectés — précisez --device ou --vidpid, ou utilisez --list pour voir.", file=sys.stderr) | ||
| + | return 3 | ||
| + | |||
| + | out = sys.stdout | ||
| + | if args.output: | ||
| + | out = open(args.output, 'w', buffering=1) | ||
| + | |||
| + | print(f"Opening {target} (non-blocking) ...", file=sys.stderr) | ||
| + | try: | ||
| + | f = open_nonblocking(target) | ||
| + | except PermissionError: | ||
| + | print("Permission refusée: lancez en root ou ajustez udev pour accéder à /dev/hidraw*", file=sys.stderr) | ||
| + | return 4 | ||
| + | except FileNotFoundError: | ||
| + | print("Fichier non trouvé:", target, file=sys.stderr) | ||
| + | return 5 | ||
| + | |||
| + | print("Entering read loop. Press Ctrl-C to stop.", file=sys.stderr) | ||
| + | |||
| + | try: | ||
| + | while True: | ||
| + | r, _, _ = select.select([f], [], [], args.interval) | ||
| + | if not r: | ||
| + | # timeout; continue to allow Ctrl-C responsiveness | ||
| + | continue | ||
| + | try: | ||
| + | data = f.read(64) # typical hid report length; if returns b'' skip | ||
| + | except BlockingIOError: | ||
| + | continue | ||
| + | if not data: | ||
| + | # no data read; continue | ||
| + | continue | ||
| + | ts = time.time() | ||
| + | print_report(ts, data, out) | ||
| + | except KeyboardInterrupt: | ||
| + | print("\nStopped by user.", file=sys.stderr) | ||
| + | return 0 | ||
| + | |||
| + | if __name__ == '__main__': | ||
| + | sys.exit(main()) | ||
| + | |||
| + | </code> | ||
| + | </panel> | ||
| + | </accordion> | ||
| + | |||
| + | |||
| + | ==== Renseignons-nous ==== | ||
| + | |||
| + | En recherchant un peu plus sur les gametrak, je note qu'il y a plusieurs versions | ||
| - Version 1 : autre facteur de forme, facile à reconnaître | - Version 1 : autre facteur de forme, facile à reconnaître | ||
| - Version 2 Rev 1.x : USB-HID compatible PS2 uniquement (*) | - Version 2 Rev 1.x : USB-HID compatible PS2 uniquement (*) | ||
| Ligne 46: | Ligne 224: | ||
| (C'est probablement une seule soudure qui fait la différence entre les version V2 Rev2 PS2 et PC/XBox) | (C'est probablement une seule soudure qui fait la différence entre les version V2 Rev2 PS2 et PC/XBox) | ||
| - | (*) La version 2 Rev 1 peut être réutilisée en refabriquant l'électronique à partir dune carte arduino ou teensy, mais ça prendrait un peu de patience et aurait aussi un coût... | + | (*) La version 2 Rev 1 peut être réutilisée en refabriquant l'électronique à partir dune carte arduino ou teensy, mais ça nécessiterait pas mal de patience et aurait aussi un coût... |
| + | ==== Hardware ==== | ||
| - | + | Avant de découvrir tout ça, je l'ai démonté pour vérifier que tout ça se passe bien au niveau des capteurs (et lire la version du pcb), à l'intérieur on trouve 2 doubles potentiometres pour XY, type joystick, assez classiques et 2 potentiometres qui mesurent l'élongation en Z avec un système de poulies. C'est quand même bien ingénieux tout ça! \\ | |
| - | Avant de découvrir tout ça, je l'ai démonté pour vérifier que tout ça se passe bien au niveau des capteurs, à l'intérieur on trouve 2 doubles potentiometres pour XY, type joystick, assez classiques et 2 potentiometres qui permettent qui mesure l'élongation en Z avec un système de poulies. C'est quand même bien ingénieux tout ça! | + | |
| En testant les potentiomètres tout fonctionne d'un point de vue mécanique et électrique | En testant les potentiomètres tout fonctionne d'un point de vue mécanique et électrique | ||
| Ligne 57: | Ligne 235: | ||
| Tout au bout de la flèche, sur le PCB, on peut lire qu'il s'agit d'une version Rev.1 | Tout au bout de la flèche, sur le PCB, on peut lire qu'il s'agit d'une version Rev.1 | ||
| + | ==== Comment reconnaître la version compatible ? ==== | ||
| + | {{:materiel:gametrak:gametrak_boite_blanc.jpg?direct&300|}} {{:materiel:gametrak:gametrak_boite_orange.jpg?direct&300|}} | ||
| + | Elle est vendue dans une boîte de couleur orange (à droite, ci-dessus), c'est la version vendue avec real world golf 2007. On peut chercher par exemple "gametrak + jeu real world golf 2007 ps2". En oct. 2025, je trouve cette version sur leboncoin pour 20e port compris, à voir si ça confirme! | ||
| - | Comment reconnaître la version compatible : elle est vendue dans une boîte d'une autre couleur! | + | |
| + | |||
| + | ==== Suite ==== | ||
| Je suivrais ce tuto pour la modif : https://x37v.com/x37v/writing/mad-catz-gametrak-mod-for-maxmsp/ | Je suivrais ce tuto pour la modif : https://x37v.com/x37v/writing/mad-catz-gametrak-mod-for-maxmsp/ | ||
| + | |||
| + | Avec comme point de départ musical ces scripts du CCRMA pour Chuck : https://ccrma.stanford.edu/courses/220a-fall-2018/homework/5/ | ||
| ===== Ressources ===== | ===== Ressources ===== | ||
| + | |||
| + | Le site de gametrak en 2003 : https://web.archive.org/web/20040206082043/http://www.game-trak.com/blackwind.htm | ||
| Différents hacks de gametrak pour en faire des contrôleurs musicaux | Différents hacks de gametrak pour en faire des contrôleurs musicaux | ||
| * https://gareth.prof/2014/10/23/gametrak-game-controller-project/ | * https://gareth.prof/2014/10/23/gametrak-game-controller-project/ | ||
| * https://www.davidgoldberg.net/gesticularcontroller | * https://www.davidgoldberg.net/gesticularcontroller | ||
| + | * https://maxkrien.com/gametrak-instrument-controller | ||
| + | * https://janoc.rd-h.com/archives/129 : fabrication d'un circuit USB sur mesure! | ||
| + | * https://github.com/anoujeremia/gametrak refabrication d'un circuit sur base teensy pour en faire un contrôleur midi | ||
| - | In2Games a par la suite développé quelques jeux pour PS2 avec des contrôleurs de mouvements originaux : | + | In2Games a par la suite développé quelques jeux pour PS2 avec des contrôleurs de mouvements originaux, sous le nom de gamme «realplay»: |
| * realplay puzzlesphere (avec une sphère sans fil) | * realplay puzzlesphere (avec une sphère sans fil) | ||
| - | * | + | * test : https://www.gamespot.com/articles/realplay-puzzlesphere-hands-on/1100-6180149/ |
| + | * realplay pool, realplay golf, realplay racing, voir https://gamefaqs.gamespot.com/games/company/76569-in2games | ||
| + | |||
| + | |||
| + | Autres développements logiciels open-source | ||
| + | * https://github.com/casiez/libgametrak | ||