J’ai récemment acheté un pédalier USB FS3_P de PC sensor. Ce pédalier peut émuler à peut près tous les scancodes et peut déclencher la sésie de plusieurs scancode à la suite. Je ne sais pas quelle est le nombre de scancode maximum que l’on peut générer. Le problème est que le logiciel de configuration tourne uniquement sous Windows et surtout qu’il ne permet pas de spécifier n’importe quel scancode. Je vais expliquer ici comment j’ai « reverse ingineeré » le protocole de communication avec le device et écrit un petit script python pour pouvoir le configurer depuis mon laptop sous Archlinux mais surtout avec n’importe quel scancode.
Ici lorsque je parle de « scancode », je parle de scancode USB.
J’ai d’abord installé VirtualBox et activé l’extension (modprob vboxdrv) me permettant de binder le pédalier sur la machine virtuel.
Pour mes tests j’ai utilisé une VM de développement/test fourni gratuitement par Microsoft.
Ces VM sont initialement destinée à tester les différentes versions d’IE.
Wireshark permet de capturer les données transitant sur de l’USB. Pour ce faire il faut charger le module usbmon. L’interface USB aparaissé alors dans le menu de Wireshark (lancé en root… je sais, c’est pas bien) en temps que « usbmon1 ».
Le truc à bien observer dans la communication est « Leftover Capture Data », je vous conseille créer un colone correspondant à cette élément dans votre interface Wireshark. Il s’agit du payload du « paquet » USB.
J’ai installé le soft de configuration sur la VM Windows, fait plusieurs tests. Puis j’ai fait des configuration très simple pendant que je capturé les traces USB avec Wireshark.
J’ai par exemple obtenu la trace suivante pour un changement de configuration (avec un layout qwerty) :
- pédale 1 : a → qwer
- pédale 2 : b → b
- pédale 3 : c → c
| No. | Source | Destination | Leftover Capture Data | Info |
|---|---|---|---|---|
| 1 | host | 7.0 | GET DESCRIPTOR Request DEVICE | |
| 2 | 7.0 | host | GET DESCRIPTOR Response DEVICE | |
| 3 | host | 1.0 | GET DESCRIPTOR Request DEVICE | |
| 4 | 1.0 | host | GET DESCRIPTOR Response DEVICE | |
| 5 | host | 7.0 | 0183080000000000 | URB_CONTROL out |
| 6 | 7.0 | host | URB_CONTROL out | |
| 7 | 7.2 | host | 466f6f7453776974 | URB_INTERRUPT in |
| 8 | host | 7.2 | URB_INTERRUPT in | |
| 9 | 7.2 | host | 63683346312e3274 | URB_INTERRUPT in |
| 10 | host | 7.2 | URB_INTERRUPT in | |
| 11 | host | 7.0 | 0182080100000000 | URB_CONTROL out |
| 12 | 7.0 | host | URB_CONTROL out | |
| 13 | 7.2 | host | 0881000400000000 | URB_INTERRUPT in |
| 14 | host | 7.2 | URB_INTERRUPT in | |
| 15 | host | 7.0 | 0182080200000000 | URB_CONTROL out |
| 16 | 7.0 | host | URB_CONTROL out | |
| 17 | 7.2 | host | 0881000500000000 | URB_INTERRUPT in |
| 18 | host | 7.2 | URB_INTERRUPT in | |
| 19 | host | 7.0 | 0182080300000000 | URB_CONTROL out |
| 20 | 7.0 | host | URB_CONTROL out | |
| 21 | 7.2 | host | 0881000600000000 | URB_INTERRUPT in |
| 22 | host | 7.2 | URB_INTERRUPT in | |
| 23 | host | 7.0 | 0180080100000000 | URB_CONTROL out |
| 24 | 7.0 | host | URB_CONTROL out | |
| 25 | host | 7.0 | 0181060100000000 | URB_CONTROL out |
| 26 | 7.0 | host | URB_CONTROL out | |
| 27 | host | 7.0 | 0604141a08150000 | URB_CONTROL out |
| 28 | 7.0 | host | URB_CONTROL out | |
| 29 | host | 7.0 | 0181080200000000 | URB_CONTROL out |
| 30 | 7.0 | host | URB_CONTROL out | |
| 31 | host | 7.0 | 0801000000000000 | URB_CONTROL out |
| 32 | 7.0 | host | URB_CONTROL out | |
| 33 | host | 7.0 | 0181080300000000 | URB_CONTROL out |
| 34 | 7.0 | host | URB_CONTROL out | |
| 35 | host | 7.0 | 0801000000000000 | URB_CONTROL out |
| 36 | 7.0 | host | URB_CONTROL out |
Après analyse on imagine le déroulement suivant :
- 1→10 : initialisation, récupération du modèle de pédalié. 466f6f745377697463683346312e3274 converti en ASCII donne « FootSwitch3F1.2t ».
- 11 : instruction de récupération de la configuration de la première pédale
- 13 : récupération de la configuration de la première pédale
- 15→22 : idem pour la deuxième et troisième pédale
- 23 : instruction de configuration de la première pédale
- 25 : configuration de la première pédale
- 27→36 : idem pour la deuxième et troisième pédale
J’ai lu certains articles intéressant sur le protocole USB :
J’ai utilisé la bibliothèque pyusb et les exemples de sa documentation.
Après de nombreux essais/erreurs j’ai pu produire un petit script python permettant de configurer le pédalier avec les chaines de caractères (qwerty) :
- 12qq[entré]
- su - test[entré]
- qQqq123qQqQq4[entré]
Celui-ci nécessite les droits d’écriture sur le device usb. Personellement je le lance en temps que « root » via sudo mais on peut faire plus propre (j’ai juste eu la flemme de chercher).
#!/usb/bin/python3
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import usb.core
import usb.util
import sys
import time
# find our device
dev = usb.core.find(idVendor=0x0c45, idProduct=0x7403)
# was it found?
if dev is None:
raise ValueError('Device not found')
for cfg in dev:
for intf in cfg:
if dev.is_kernel_driver_active(intf.bInterfaceNumber):
try:
dev.detach_kernel_driver(intf.bInterfaceNumber)
except usb.core.USBError as e:
sys.exit("Could not detach kernel driver from interface({0}): {1}".format(intf.bInterfaceNumber, str(e)))
dev.set_configuration()
msg = [0x01, 0x83, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
dev.read(0x82, 16, 100)
time.sleep(1)
msg = [0x01, 0x82, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
dev.read(0x82, 32, 1000)
time.sleep(1)
msg = [0x01, 0x82, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
dev.read(0x82, 32, 100)
time.sleep(1)
msg = [0x01, 0x82, 0x08, 0x03, 0x00, 0x00, 0x00, 0x00]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
dev.read(0x82, 32, 100)
time.sleep(1)
msg = [0x01, 0x80, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
time.sleep(1)
# sometime you have to adapte (lower) "number_of_scancode"
# else the footswitch generate unwanted shift scancode at
# the end of the of the generation sequence
##
# slot 1
##
slot = 1
# 5 → 4
number_of_scancode = 4
msg = [0x01, 0x81, number_of_scancode + 2, slot, 0x00, 0x00, 0x00, 0x00]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
time.sleep(1)
# 12qq[enter]
# 1 2 q q [enter]
msg = [number_of_scancode + 2, 0x04, 0x59, 0x5a, 0x14, 0x14, 0x28, 0x00]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
time.sleep(1)
##
# slot 2
##
slot = 2
number_of_scancode = 10
msg = [0x01, 0x81, number_of_scancode + 2, slot, 0x00, 0x00, 0x00, 0x00]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
time.sleep(1)
# su - test[enter]
# s u - t
msg = [number_of_scancode + 2, 0x04, 0x16, 0x18, 0x2c, 0x2d, 0x2c, 0x17]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
time.sleep(1)
# e s t [enter]
msg = [0x08, 0x16, 0x17, 0x28, 0x00, 0x00, 0x00, 0x00]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
time.sleep(1)
##
# slot 3
##
slot=3
# 14 → 12
number_of_scancode = 12
msg = [0x01, 0x81, number_of_scancode + 2, slot, 0x00, 0x00, 0x00, 0x00]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
time.sleep(1)
# qQqq1230xaPas5[enter]
# q Q q q 1 2
msg = [number_of_scancode + 2, 0x04, 0x14, 0x94, 0x14, 0x14, 0x59, 0x5a]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
time.sleep(1)
# 3 q Q q Q q 4 [enter]
msg = [0x5b, 0x14, 0x94, 0x14, 0x94, 0x14, 0x5c, 0x28]
dev.ctrl_transfer(0x21, 9, 0x200, 1, msg)
time.sleep(1)
dev.reset()
usb.util.dispose_resources(dev)
for cfg in dev:
for intf in cfg:
if dev.is_kernel_driver_active(intf.bInterfaceNumber):
try:
dev.attach_kernel_driver(intf.bInterfaceNumber)
except usb.core.USBError as e:
sys.exit("Could not attach kernel driver from interface({0}): {1}".format(intf.bInterfaceNumber, str(e)))
Si vous voulez le réutiliser pour configérer le pédalier avec vos propre chaines de caractères je vous encourage à lire la page « Yubikey, bépo et esperluette » qui indique comment récupérer les scancodes des touches. Comme pour la Yubikey, il faut ajouter « 0x80 » au scancode pour que le pédalier produise un appui/relachement de la touche shift avant et après la touche correspondante.
Je n’ai pas encore trouvé pourquoi et de quel manière il faut diminuer la valeur du nombre de scancode par rapport à la réalité. Si vous utilisé ce script, vérifiez que le pédalier produit bien les scancodes désiré et modifiez le nombre de scancodes si celui-ci produit trop de shift. En effet, si j’utilise le nombre de scancodes réel, le pédalier produit en fin de séquence des appuis (sans relachement) de la touche shift.