MZ Platformの機能開発記録

FeliCaカードID取得

felicalibとJNAを利用して、JavaおよびMZでFeliCaのカードID文字列を取得する方法を紹介します。

動作条件

FeliCaのカードID文字列を取得してJavaおよびMZで利用する方法を検討したのでその情報を公開します。 ここでは、felicalibJNAを利用しています。 felicalibを利用しているので、対象となるFeliCaリーダはSONYのPaSoRi限定となります。 動作PCにはPaSoRiに付属のFeliCaポートソフトウェアがインストールされている必要もあります。

なお、検討した時点ではPaSoRi RC-S370を購入して動作確認しましたが、 この製品はすでに生産中止となっています。PaSoRiシリーズの後継機での動作確認はしておりません。 FeliCaカードとしてはSuicaとPASMOのカード、 おサイフケータイ対応の当時の携帯電話機で動作確認しましたが、 最近のNFC対応のスマートフォンでは動作しませんでした。 (2013/10/15追記)FeliCaポートソフトウェアを最新のNFCポートソフトウェアにアップデートしたところ、 上記のPaSoRiでNFC対応のスマートフォンでもID文字列を取得できました。

また、ここではFeliCaカードに含まれるお金や履歴の情報は扱いません。 カードIDを取得してその文字列だけで個人識別などに用いることを想定しています。

以上のように、利用できる条件が限られているため、 ここで紹介する機能はMZの標準コンポーネントとして配布に含めていません。 独自にコンポーネントを作成して利用したい方のために情報だけ公開しておきます。

FeliCaリーダクラス

下記のようなFeliCaリーダクラスを作成します。 MZで利用する場合はこのクラスをコンポーネントとして作成します。 JNAでfelicalibの関数を呼び出す内部インタフェースの作成がポイントです。

JNA用にjna.jarをJavaのクラスパスに配置して、 felicalib用にfelicalib.dllを実行位置などに配置する必要があります。

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;

public class FeliCaReader {

	//
	// JNAによるfelicalibの関数呼び出し用インタフェース
	//
	private interface CLibrary extends Library {

		// 呼び出し用のインスタンス
		static CLibrary INSTANCE =
			 (CLibrary) Native.loadLibrary("felicalib",CLibrary.class);

		// 呼び出す関数の宣言:felicalibの関数
		Pointer pasori_open(char[] c);
		int pasori_init(Pointer pasori);
		void pasori_close(Pointer pasori);
		Pointer felica_polling(Pointer pasori, short sys, byte rfu, byte time);
		void felica_free(Pointer felica);
		void felica_getidm(Pointer felica, byte[] buf);
	}

	//
	// (以降で紹介する変数とメソッドを宣言)
	//
}

このクラスに、以降で紹介するメンバ変数とメソッドを追加する形で、FeliCa用の機能を実現します。

ポート接続

カードIDを取得するために、まずはポート接続をする必要があります。 また、終了時にはポートを切断する必要もあります。


private Pointer pasori; // ポート接続用のハンドル

//
// ポート接続
//
public boolean open() {

	if (isOpened()) return true; // すでに開いている場合

	pasori = CLibrary.INSTANCE.pasori_open(null); // ポート接続

	if (pasori==Pointer.NULL) { // ポート接続失敗
		return false;
	}
	if (CLibrary.INSTANCE.pasori_init(pasori)!=0) { // ポート初期化失敗
		close(); // ポート切断
		return false;
	}
}

//
// ポート接続判定
//
public boolean isOpened() {

	if (pasori==Pointer.NULL) return false;

	return true;
}

//
// ポート切断
//
public void close() {
		
	if (isOpened())
		CLibrary.INSTANCE.pasori_close(pasori);
		
	pasori = Pointer.NULL;
}

カードID取得

ポートに接続できたら、下記のメソッドでカードID文字列を取得することができます。 ただし、このメソッドはカードがリーダから認識できる状態で呼び出す必要があります。


//
// カードID文字列取得:接続していなければ空文字
//
public String getID() {

	if (isOpened()==false) return ""; // ポート接続されていなければ終了

	// カードの認識(ポインタを取得)
	// (2014/08/18修正)システムコード指定に誤りあり:正しくは0xFFFF
	//Pointer felica = CLibrary.INSTANCE.felica_polling(pasori,(short)0xFF,(byte)0,(byte)0);
	Pointer felica = CLibrary.INSTANCE.felica_polling(pasori,(short)0xFFFF,(byte)0,(byte)0);

	if (felica==Pointer.NULL) return ""; // カードを認識できなければ終了
			
	// カードID(バイト値)を取得
	byte [] buf = new byte[8];
	CLibrary.INSTANCE.felica_getidm(felica, buf);

	// ここでカードのポインタを解放
	CLibrary.INSTANCE.felica_free(felica);

	// カードIDを文字列に:16進数表記(英大文字/数字の2文字)×8
	StringBuffer sbuf = new StringBuffer("");
	for (int i=0; i<8; i++) {
		String hex = Integer.toHexString(buf[i]);
		if (hex.length()==1) sbuf.append("0"); // 1文字の場合前に0を付加
		if (hex.length()>2) // 3文字以上の場合は最後の2文字のみ使用
			hex = hex.substring(hex.length()-2);
			// ※buf[i]が負の値のとき、hexがfffffffcのような8文字になることへの対応
		sbuf.append(hex);
	}
	return sbuf.toString().toUpperCase(); // 英字は大文字に
}

この機能を利用したアプリケーションの動作イメージは下記のようになります。

上右図のようにカードがリーダから検出できる状態にしてID取得ボタンを押すと、 ダイアログでID文字列が表示されます。

カード検出

スレッドを用いて、カードを検出したらID文字列を取得する機能を下記のように作成することができます。


private long interval = 500; // スレッドの実行間隔
private boolean waiting = false; // スレッド実行フラグ

//
// 別スレッドでカードID検出待機
//
public void startWaiting() {
		
	if (waiting) return; // すでに実行中の場合ここで終了 
		
	if (isOpened()==false) return; // 接続中でない場合ここで終了 
		
	// スレッドの作成
	thread = new Thread( "felica_waiting" ){
		public void run(){
			try{
				String prev_id = ""; // 前回取得したIDを記憶

				while (waiting) { // フラグが立っている間は繰返し実行
						
					// ID取得実行の間隔調整
					Thread.sleep(interval);
						
					// ID取得実行:取得できない場合は空文字
					String byte_str = getID();

					if (prev_id.equals(byte_str)==false)
						dataUpdated(byte_str); // 前回取得から変更あり

					prev_id = byte_str; // 取得したIDを記憶
				}
			} catch ( Exception e ){
				waiting = false; // 例外発生したらループ終了
			}
		}
	};

	// フラグを立ててスレッドを実行
	waiting = true;
	thread.start();
}

//
// スレッドを実行するためのフラグ消去
//
public void stopWaiting() {
	waiting = false;
}

// 
// 取得したID文字列に変更があった場合の処理
// 
private void dataUpdated(String id) {
	...
}

この機能を利用したアプリケーションの動作イメージは下記のようになります。

待機開始ボタンを押して検出待機状態にして、下図右のようにカードが検出されないときはIDを空欄表示とし、

下図右のようにカードが検出されたらIDを表示するような動作が実現できます。

作成日 2013-10-09

最終更新日 2014-08-18