LoginSignup
34
27

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-02-01

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

デバイスの接続確認

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

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

転送方法

転送方法には次の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

34
27
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
27