#!/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())