2016年10月21日金曜日

Raspberry Pi で複数のACRリーダを使う

Raspberry Pi で ACR-122U のリーダをPC/SCで使う場合、SCardListReaders でリーダ名を取得して使用します。で、2台のリーダを接続すると、取得するリーダ名も2つになります。

例) ACS ACR122U PICC 00 00 と ACS ACR122U PICC 01 00

こんな風に後ろの数字が1番、2番と増えていきます。で、後のプログラムではこの名前を指定することでリーダを指定できるわけですね。

なーんだ、リーダいっぱい繋いだって問題無いじゃん、使えるじゃん、へへーんだ、ちょろいちょろい、と日々何事もなく?過ごしていたわけですがそれがなんと大間違いということがわかりました。

実はこの後ろにつく番号、システムをリブートすると変わることがあるのです。変わるといっても3番、4番とかが出てくるわけではなくて、出てくるのは0番と1番なのですが、さっき0番だったリーダがリブートすると1番になってたとかそういうことがあるわけです。

これは一大事です。何とか各リーダを識別する方法を見つけなければなりません。パソリでは SCardGetAttrib で SCARD_ATTR_VENDOR_IFD_SERIAL_NO を指定すればリーダのシリアル番号を取得できるそうですので、ACRリーダも試してみましたがこれがことごとく SCARD_E_UNEXPECTED を返してきて取得できません。どうもACRリーダがこれに対応していないようです。

さー、困りました。とりあえずUSBの接続状態を見てみます。

pi@raspberrypi:~ $ lsusb -t
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=dwc_otg/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/5p, 480M
        |__ Port 1: Dev 3, If 0, Class=Vendor Specific Class, Driver=smsc95xx, 480M
        |__ Port 3: Dev 4, If 0, Class=Hub, Driver=hub/5p, 480M
            |__ Port 1: Dev 5, If 0, Class=Human Interface Device, Driver=usbhid, 480M
            |__ Port 2: Dev 6, If 0, Class=Chip/SmartCard, Driver=usbfs, 12M
            |__ Port 4: Dev 7, If 0, Class=Chip/SmartCard, Driver=usbfs, 12M

複数のACRリーダをつなぐと給電が足りないため、AC付きのUSBハブにリーダを接続しています。

ここには Bus、Port、Dev と3つの番号が出ています。これが使えないか?ということで調べてみると Dev番号が libusb の usb_device->filename で取得できました。この番号は階層の高い方、Portの若い方から割り振られているような気がしますね!きっとそうに違いない!

というわけで PC/SC は諦め、libusb で直接USBにアクセスする方法に変えてみました。

まず Dev番号の若い順に usb_device を取得します。

struct usb_bus *bus;
struct usb_device *dev;

struct usb_device *rdev[READER_NUM];

usb_init();

if (!usb_get_busses()) {
usb_find_busses();
usb_find_devices();
}

min = 0;
for (i = 0; i < READER_NUM; i++) {
max = 999;
for (bus = usb_get_busses(); bus; bus = bus->next) {
for (dev = bus->devices; dev; dev = dev->next) {
if (dev->descriptor.idVendor == 0x072f && dev->descriptor.idProduct == 0x2200) {
if (atoi(dev->filename) < max && atoi(dev->filename) > min) {
rdev[i] = dev;
max = atoi(dev->filename);
}
}
}
}
min = max;
seq[i] = 0x00;
}

で、詳細の説明は省きますがUSBに直接データを送受信します。devに上記で取得した usb_device を指定します。全部のリーダで読む場合は、これを繰り返せばOKです。

usb_dev_handle *handle;
// EndPointを取得
struct usb_config_descriptor *config = &dev->config[0];
struct usb_interface *interface = &config->interface[0];
struct usb_interface_descriptor *altsetting = &interface->altsetting[0];
struct usb_endpoint_descriptor *endpoint = &altsetting->endpoint[0];
uint8_t ep = endpoint->bEndpointAddress;

struct usb_endpoint_descriptor *out_ep = &altsetting->endpoint[1];
struct usb_endpoint_descriptor *in_ep = &altsetting->endpoint[2];

unsigned char buf[256];
ssize_t read_size;
int i,j;

unsigned char command_D[] = { 0x6f, 0x05, 0x00, 0x00, 0x00, 0x00, 0xff, 0x04, 0x00, 0x00, 0xff, 0xca, 0x00, 0x00, 0x00 };
unsigned char command_A[] = { 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00 };

handle = usb_open(dev);

usb_claim_interface(handle, altsetting->bInterfaceNumber);
// リーダの初期化コマンド これで赤ランプが点く
usb_command(handle, out_ep, in_ep, (char *)command_A, sizeof(command_A), (char *)buf, readerIndex);

// カードを認識すると 0x50 0x03 が飛んできて、カードが無くなると 0x50 0x02 が飛んでくるのを読み捨て
read_size = usb_bulk_read(handle, ep, (char *)buf, endpoint->wMaxPacketSize, 1000);
// Get UID コマンドを送信
read_size = usb_command(handle, out_ep, in_ep, (char *)command_D, sizeof(command_D), (char *)buf, readerIndex);
if (read_size > 12 && buf[read_size - 2] == 0x90 && buf[read_size - 1] == 0x00) {
for (j = 0; j < read_size - 12; j++)
sprintf(UID, "%s%02X", UID, buf[10 + j]);
printf("UID = %s\n", UID);
}

usb_resetep(handle, ep);
usb_release_interface(handle, altsetting->bInterfaceNumber);

usb_close(handle);

上記で使っている usb_command 関数は以下の通り

int usb_command(usb_dev_handle *dev, struct usb_endpoint_descriptor *out_ep, struct usb_endpoint_descriptor *in_ep, char *send, int size, char *recv, int readerIndex)
{
ssize_t ret;
int i;

seq[readerIndex]++;
*(send + 6) = seq[readerIndex];

ret = usb_bulk_write(dev, out_ep->bEndpointAddress, send, size, 1000);
if (ret < 0) {
printf("Write Error.(%d,%s)\n",ret, usb_strerror());
return ret;
}

while (1) {
ret = usb_bulk_read(dev, in_ep->bEndpointAddress, recv, in_ep->wMaxPacketSize, 5000);
if (ret <= 0) {
printf("Read Error.(%d,%s)\n",ret, usb_strerror());
return ret;
}
if (*(recv + 6) == seq[readerIndex])
break;
}
return ret;
}

これでめでたく Dev 番号の若い順を意識したリーダへのアクセスが可能になりました。まぁ、リブートしてもDev番号(というか順番)は変わらないよねという前提がありますがー(泣。