For a work’s project I needed to use two magnetic card readers ; our usage was depending on which one is used and the card’s ID. So I sourced a magnetic card reader that outputs the Id through a TTL serial line and then I could manage using an arduino both serial lines and get them to work how we needed. But what was my surprise to get USB HID readers once I receipt the package !
So I plug them in, and well, they worked out of the box… but both of them output Ids on the console the same way. We were on a rush on the project, so we had no time to send them back.
So my challenge was : How can we differenciate USB HID readers that act as keyboards ?
Challenge accepted !
So I’ve look on the great Internet how people managed to work out with USB HID magnetic card readers, and after lots of useless reading I finally found on Micah Carrick’s site how he managed to use a MagTek reader to work with python.
Good news was that at first I could easily differentiate both readers by using PyUSB 1.0 :
import usb.core import usb.util VENDOR_ID = 0x1130 PRODUCT_ID = 0x0001 DATA_SIZE = 167 device = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID)
And to connect two readers, nothing is easier ! Just add the parameter
device = usb.core.find(find_all=True, idVendor=VENDOR_ID, idProduct=PRODUCT_ID)
device is a list of all device with same vendor and product id connected to
the system, so you can differentiate
Then, to unload the kernel driver, so it does not output anymore the Ids on the console (and thus have access to the reader).
if device.is_kernel_driver_active(0): try: device.detach_kernel_driver(0) except usb.core.USBError as e: sys.exit("Could not detatch kernel driver: %s" % str(e)) …
And finally, we have to get the endpoint, which is the mapping to the actual device :
endpoint = device[(0,0)]
To read the output, the following is what you need :
(the whole actual loop is in the source)
But, once I’ve been able to read things from the card… what was my surprise to only get garbage ! The data was totally messed up.
So I started to look around on Internet, and nothing was to be found, seems I was the only one who wanted to read directly on such a device.
What can be done then ?
well, get back to what I know :
- get the datasheet of the reader
- learn what the kernel does to get something out of that
After some searching, I finally found it. But nothing very helpful over there
So my last luck was reverse engineering the kernel. So I started with the USB protocol specification :
after reading some, I was quite afraid, because it has been hard to actually communicate actively with the reader. But after turning around with digging in the protocol, I chose to dig in the kernel’s code, and what was my luck was to stumble on the following code :
Finally I found the structure of what the HID device outputs : the first byte
is the modifier keycode, and the following byte is the actual character
keycode. Then, the keycode is matched to the
usb_kbd_keycode array, and again
to a layout array (in another module) which matches your country’s keyboard.
After some matching, I got the following mapping that works out of the box with my reader:
key_pages = [ '', '', '', '', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\n', '^]', '^H', '^I', ' ', '-', '=', '[', ']', '\\', '>', ';', "'", '`', ',', '.', '/', 'CapsLock', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'PS', 'SL', 'Pause', 'Ins', 'Home', 'PU', '^D', 'End', 'PD', '->', '<-', '-v', '-^', 'NL', 'KP/', 'KP*', 'KP-', 'KP+', 'KPE', 'KP1', 'KP2', 'KP3', 'KP4', 'KP5', 'KP6', 'KP7', 'KP8', 'KP9', 'KP0', '\\', 'App', 'Pow', 'KP=', 'F13', 'F14' ]
A lot of thanks to Micah Carrick for his blog post and his code.
- full source code is on my github’s page