SONY の RC-S380 を入手したので、とりあえず Suica の IDm を読み込んでみようとやってみました。
うちは Mac しかないので、Mac でも出来るかどうか確認もかねて。
libusb のインストール
まずはこれのインストールから。
$ brew install libusb
しかし、brew でインストールしてもなんかうまくいかなかったので、ソースから。
→ https://sourceforge.net/projects/libusb/files/libusb-1.0/libusb-1.0.21/
$ tar zxvf libusb-1.0.21.tar.bz2
$ cd libusb-1.0.21
$ ./configure
$ make
# make install
#brew でのインストールがうまくいかなかったのは、どうやら オブジェクトファイルが古いからみたいです。ソースからインストールすると最新になるので、それでいけるようになります。
#ちょっと忘れたので一応メモ。もしかしたら勝手になってるかも。。
#コンパイルのために /usr/local/include に brew でインストールしたやつのリンクを張っておきます。
# ln -s /usr/local/Cellar/libusb/1.0.21/include/libusb-1.0 /usr/local/include/libusb-1.0
デバイスの接続確認
まずはデバイスを認識するかどうかの確認です。
ここまではネット上にも情報があるので比較的簡単にできました。
/* usb test with libusb-1.0 */
#include <iostream>
#include "libusb.h"
int main() {
libusb_device **devs;
libusb_device *dev;
libusb_device_descriptor desc;
libusb_device_handle *dh;
uint8_t path[8];
uint8_t sdat[255];
int r, cnt;
r = libusb_init(NULL); // initalize
if(r < 0) return r;
cnt = libusb_get_device_list(NULL, &devs);
if(cnt < 0) return cnt;
for(int i = 0; i < cnt; i++){
dev = devs[i];
r = libusb_get_device_descriptor(dev, &desc);
if(r < 0){
std::cout << "device get error..." << std::endl;
return r;
}
// show device description
printf("%04x/%04x (bus %d, device %d)", desc.idVendor, desc.idProduct,
libusb_get_bus_number(dev), libusb_get_device_address(dev));
r = libusb_get_port_numbers(dev, path, sizeof(path));
if(r < 0) return r;
printf(" path: %d", path[0]);
for(int j = 1; j < r; j++){
printf(".%d", path[j]);
}
// show spec string
libusb_open(dev, &dh);
// manufacturer, product, serialnumber
r = libusb_get_string_descriptor_ascii(dh, desc.iManufacturer,
(unsigned char *)sdat, sizeof(sdat));
if(r > -1) printf(" %s", sdat);
r = libusb_get_string_descriptor_ascii(dh, desc.iProduct,
(unsigned char *)sdat, sizeof(sdat));
if(r > -1) printf(" %s", sdat);
r = libusb_get_string_descriptor_ascii(dh, desc.iSerialNumber,
(unsigned char *)sdat, sizeof(sdat));
if(r > -1) printf(" %s", sdat);
libusb_release_interface(dh, 0);
libusb_close(dh);
printf("\n");
}
libusb_free_device_list(devs, 1);
libusb_exit(NULL); // exit
return 0;
}
で、コンパイルして実行してみます。
$ g++ devicecheck.cpp -I/usr/local/include/libusb-1.0 -L/usr/local/lib -lusb-1.0 -o devicecheck
$ ./devicecheck
054c/06c3 (bus 20, device 10) path: 1 SONY RC-S380/P 0577124
05ac/8406 (bus 20, device 8) path: 12 Apple Card Reader 000000000820
05ac/828f (bus 20, device 6) path: 3 Apple Inc. Bluetooth USB Host Controller
0a5c/4500 (bus 20, device 3) path: 3 Apple Inc. BRCM20702 Hub
はい、ちゃんと認識してますね。
最初の 054c/06c3
は前者が VENDOR_ID で 後者が PRODUCT_ID になります。
これは後々使うので覚えておきましょう。
コンパイルで苦戦したのは、-lusb-1.0
のところです。
普通に -lusb とやってもだめで、 -1.0 が必要というのがわかるまでちょっと苦労しました (^^;
USB について簡単に
libusb の関数を扱う時に少しUSBについて知っておかなければいけないことがあったので、調べてみました。
転送方法
転送方法には次の4つの方法があるようです。
- コントロール転送
- バルク転送
- インタラプト転送(割り込み転送)
- アイソクロナス転送(リアルタイム転送)
コントロール転送はすべてのデバイスが扱える転送方法です。
デバイスの情報取得や設定などを行う時に使うようです。データの送受信も可能です。
バルク転送は大量のデータを送受信する時に使います。
割り込み転送は、名前と違って実質はバルク転送のようなものです。データの受送信が発生した時に割り込み処理が発生する、ということはなくて、自分で監視する必要があるようです。
アイソクロナス転送は、カメラや音声など、高速大量の通信を行う時に使われるようです。チェックサム的なものがない分高速に転送が可能ということです。その代わり多少の間違いがあっても気にならない用途向けです。
エンドポイント
USBはデータの送受信を行うためにちょっとした場所を用意します。
その出入り口のことを「エンドポイント」と言うようです。
データをデバイスに送りたいときは、このエンドポイントが指す場所にデータを格納すると、すらすらと送られます。
逆にデバイスからのデータもエンドポイントが指す場所に格納されるので、そこをみに行くことでデータを取得します。
前者を ENDPOINT_OUT、後者を ENDPOINT_IN として区別します。
libusb では
libusb では、デバイス情報取得などで使用するコントロール転送は関数がカバーしてくれているので、あまり気にする必要はないみたいです。
#libusb_get_device_descriptor() など
バルク転送もインタラプト転送も関数があるので、特に問題なく利用できます。
バルク転送手順
デバイスをオープンして、そのデバイスのエンドポイント(IN、OUT)のアドレスを取得します。
あとは、取得したエンドポイントアドレスに対してデータの送受信を行います。
RC-S380 を制御する
Suica を読み込むためには、RC-S380 に対してデータの送受信をする必要があります。
デバイスの基本情報は、上記のソースコードで見ることはできますが、実際にはデバイスに対して制御コマンドを投げたりデータを取得したりする必要があります。
というわけで、SONY のホームページから各種資料をダウンロードして確認します。
→ https://www.sony.co.jp/Products/felica/business/tech-support/index.html
がしかし、これらはなんか古い情報のようで、RC-S380 では当てはまりませんでした。
というわけで、ネット上を探すのですが、出てくる情報は 「python を使って読み込んだ」というものばかりで、ちょっと求めているものと違うので、苦戦しました。
#RC-S380 は SONY NFC Port-100 chipset というのを使っているみたいです。
#このキーワードで検索すると ここ が見つかりましたが、これって linux カーネルのソース?
nfcpy の中身を見る
いろいろ探して見ましたが、python しかないので、諦めて中身をみてみることにしました。
→ https://github.com/nfcpy/nfcpy/blob/master/src/nfc/clf/rcs380.py
もうまさにこれ!これが欲しかった情報です!
というわけで、これを参考に C++ にしてみました。
Suica の IDm を取得する
ソースが長いみたいで Qiita に上げられなかったので Github に置いてます。
みてください。
#Qiita に上げられなかったのは タグが5つ以上あったからみたいでした。。
#でもまあ Github に上げたので、このままにしておきます :)
get_usb_information
ここではデータを送受信するためのエンドポイントを取得しています。
packet_send
純粋にデータを送受信しているところです。
RC-S380 は コマンド送信 → ack/nack受信 → データ受信
という流れになっているので、受信を2回行なっているのがポイントです。
packet_write
コマンドを送信するためのパケットを作成して packet_send に送っています。
RC-S380 のパケットは、
ヘッダー + データ長さ + データ長さのチェックサム + データ + データのチェックサム + フッター
のようです。
ヘッダー: 0x00 0x00 0xff 0xff 0xff
データ長さ: コマンド長さ + 1
データ: コマンド列
フッター: 0x00
チェックサムは
(0x100 - データの1バイト毎の合計 ) % 0x100
→ checksum()
あとコマンド列の先頭に 0xd6 を追加するのがポイントです。
データはこの 0xd6 も含めて計算します。
packet_init
RC-S380 を制御するために最初に投げるコマンドです。
#デバイスに対して ack を送信するみたいです。
#戻りはありません。
packet_xxx
以下はコマンドを投げるところです。
pakcet_init をしてから以下順に投げると良いです。
packet_setcommandtype
packet_switch_rf
packet_inset_rf
packet_inset_protocol_1
packet_inset_protocol_2
packet_sens_req
最後 packet_sens_req で受信データが取得できるので、それを解析して表示します。
main
引数をつけて Type-F(FeliCa) と Type-B(運転免許証など) の情報を読み取れるようにしてみました。
#ちなみに運転免許証の詳細情報はパスワードで守られてるのでこのソースでは見ることはできません。
##実行してみる
というわけで、コンパイルして実行してみます。
$ g++ getdeviceid.cpp -I/usr/local/include/libusb-1.0 -L/usr/local/lib -lusb-1.0 -o getdeviceid
まずは Suica(Type-F) から
$ ./getdeviceid -F
NFC Type-F scanning...
IDm: 0114xxxxxxxx9305
PMm: 1000xxxxxxxx0000
次に運転免許証(Type-B)
$ ./getdeviceid -B
NFC Type-B scanning...
NFCID: 9xxxxxx4
Application Data: cxxxxxx0
Protocol Info: 7xxxxxx3
Type-A はコマンドつけて見ましたが、不完全なので、注意してください (^^
課題
Suica などの FeliCa はうまくいきました。
運転免許証も私のは読み取れましたが、他の人のをかざして見たらうまくいきませんでした。
ネット上では、発行した地域によって対象周波数が違う、みたいな記述もあるので、その辺りについても調べてみる必要がありそうです。
あと、時間があれば Type-A も出来るようにしたいですね。
参考
libusb関係
USB関係
http://www.picfun.com/usb02.html
http://www.kumikomi.net/archives/2007/03/22usb1.php
FeliCa関係
https://qiita.com/saturday06/items/333fcdf5b3b8030c9b05
https://github.com/nfcpy/nfcpy/blob/master/src/nfc/clf/rcs380.py