今更ですが、SONY RC-S380 で Suica の IDm を読み込んでみた


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
```

## デバイスの接続確認
まずはデバイスを認識するかどうかの確認です。
ここまではネット上にも情報があるので比較的簡単にできました。

```cpp:devicecheck.cpp
/* 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について知っておかなければいけないことがあったので、調べてみました。

### 転送方法
転送方法には次の４つの方法があるようです。

 + コントロール転送
 + バルク転送
 + インタラプト転送（割り込み転送）
 + アイソクロナス転送（リアルタイム転送） 

コントロール転送はすべてのデバイスが扱える転送方法です。
デバイスの情報取得や設定などを行う時に使うようです。データの送受信も可能です。

バルク転送は大量のデータを送受信する時に使います。

割り込み転送は、名前と違って実質はバルク転送のようなものです。データの受送信が発生した時に割り込み処理が発生する、ということはなくて、自分で監視する必要があるようです。

アイソクロナス転送は、カメラや音声など、高速大量の通信を行う時に使われるようです。チェックサム的なものがない分高速に転送が可能ということです。その代わり多少の間違いがあっても気にならない用途向けです。


### エンドポイント
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 というのを使っているみたいです。
＃このキーワードで検索すると [ここ](https://github.com/torvalds/linux/blob/master/drivers/nfc/port100.c) が見つかりましたが、これって linux カーネルのソース？

## nfcpy の中身を見る
いろいろ探して見ましたが、python しかないので、諦めて中身をみてみることにしました。
 → https://github.com/nfcpy/nfcpy/blob/master/src/nfc/clf/rcs380.py
 
もうまさにこれ！これが欲しかった情報です！
というわけで、これを参考に C++ にしてみました。

## Suica の IDm を取得する
ソースが長いみたいで Qiita に上げられなかったので Github に置いてます。
みてください。

https://github.com/ysomei/test_getnfcid/blob/master/getdeviceid.cpp

＃Qiita に上げられなかったのは タグが５つ以上あったからみたいでした。。
＃でもまあ 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関係
http://libusb.sourceforge.net/api-1.0/index.html

### 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