Felicaリーダ及びメール送信トリガーの作成(Felica編)



GitHub

【概要】:
Felicaのカードリーダにアクセスするアンマネージのライブラリを作ります。
C/C++言語での実装です。


ヘッダーファイルFelicaRW.h

#pragma once

#include <Windows.h>
#include "felica_nfc_library.h"

#pragma comment(lib ,"felica_nfc_library.lib")
#pragma warning(disable : 4996)


class Felica
{

private:

// FelicaRW
#define IDM_SIZE        (8)         // IDm のサイズ
#define BLOCK_SIZE      (16)        // ブロック(読み書きの最小単位)のサイズ

#define SYSTEM_CODE     (0x88b4)    // 0x88b4 = FeliCa Lite-S のシステムコード
#define SERVICE_CODE_RW (0x0009)    // 0x0009 = Random Read/Write のサービスコード
#define SERVICE_CODE_RO (0x000B)    // 0x000B = Random Read Only のサービスコード

public:
bool PrintNfcLastErr(char* pFuncName);
bool MyPrintHex(unsigned char *pBuf, int iLen);
bool MyPolling(unsigned short usSystemCode, unsigned char pIDm[IDM_SIZE]);
bool MyWriteWithoutEncryption(unsigned char *pIDm, unsigned short usServiceCode, unsigned char ucBlockOffset, unsigned char *pDataW);
bool MyReadWithoutEncryption(unsigned char *pIDm, unsigned short usServiceCode, unsigned char ucBlockOffset, unsigned char *pDataR);
void FelicaRW_main(int block, unsigned char* wData, bool read);
void SetData();
unsigned char* GetData();
unsigned char* GetName();
};


インプリメンテーションファイルFelicaRW.cpp

このクラス関数の定義ファイルは、すでにWeb上で公開されているコードを改変して作ったものです。
このサイトはFelicaを扱うにあたって、とても参考になります。時間があるときに是非一読されることをおススメします。
// FelicaRW.cpp : 実装ファイル
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "FelicaRW.h"

#pragma warning(disable : 4996)

// FelicaRW
unsigned char account[16];
unsigned char domain[16];
unsigned char user_id[16];
unsigned char password[16];
unsigned char str[100];

unsigned char name[16];

unsigned char* ustrcat(unsigned char *dest, const unsigned char *src) {
	unsigned int length_1 = 0;
	unsigned int length_2 = 0;
	// find the end of the first unsigned char string
	for (; dest[length_1] != '\0'; length_1++);
	//input unsigned chars into the string from that position to the end
	for (; src[length_2] != '\0'; length_2++)
		dest[length_1 + length_2] = src[length_2];
	// place a NULL terminating character at the end
	dest[length_1 + length_2] = '\0';
	return dest;
}

unsigned char* Felica::GetName()
{
	return name;
}

unsigned char* Felica::GetData()
{
	return str;
}

void Felica::SetData()
{
	strcpy((char*)str, (char*)account);

	strcat((char*)str, ",");

	strcat((char*)str, (char*)domain);

	strcat((char*)str, ",");

	strcat((char*)str, (char*)user_id);

	strcat((char*)str, ",");

	strcat((char*)str, (char*)password);
}

bool Felica::PrintNfcLastErr(char* pFuncName)
{
	unsigned long pErrInfo[2];
	bool bSuccess;

	bSuccess = felicalib_nfc_get_last_error(pErrInfo);
	if (bSuccess)
	{
		printf("%s でエラー発生 [%x, %x].\n",
			pFuncName,
			pErrInfo[0],    // NFCアクセスライブラリエラーコード
			pErrInfo[1]);   // ドライバエラーコード
	}
	else
	{
		printf("%s でエラー発生. エラー情報取得失敗.\n",
			pFuncName);
	}

	return true;
}

bool Felica::MyPrintHex(unsigned char *pBuf, int iLen) {

	char buf[16]; 
	for (int i = 0; i < iLen; i++)
	{
		sprintf(buf, "%02x ", pBuf[i]);
		//strcat(po, buf);
	}
	sprintf(buf, "%s", "\r\n");
	//strcat(po, buf);

	return true;
}

bool Felica::MyPolling(unsigned short usSystemCode,
	unsigned char pIDm[IDM_SIZE]) {
	bool bSuccess;

	// Polling コマンドの組み立て
	unsigned char pCom[] = {
		0x00,           // データ長
		0x00,           // コマンドコード
		0x00, 0x00,     // システムコード
		0x00,           // リクエストコード
		0x00            // タイムスロット
	};
	unsigned char pRes[512];

	unsigned short usComLen = sizeof(pCom);
	unsigned short usResLen = sizeof(pRes);

	pCom[0] = sizeof(pCom);

	pCom[2] = usSystemCode >> 8;
	pCom[3] = usSystemCode & 0xFF;

	// コマンドの発行
	bSuccess = felicalib_nfc_thru(pCom, usComLen, pRes, &usResLen);
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_thru(polling)");
		return false;
	}

	// レスポンスのチェック
	if (usResLen != 1 + 1 + 8 + 8)
	{
		printf("polling のレスポンス長 %d が不正 #1\n", usResLen);
		return false;
	}
	if (pRes[0] != 1 + 1 + 8 + 8)
	{
		printf("polling のレスポンス長 %d が不正 #2\n", pRes[0]);
		return false;
	}
	if (pRes[1] != 0x01)
	{
		printf("polling のレスポンスコード %d が不正\n", pRes[1]);
		return false;
	}

	// IDm のコピー
	memcpy_s(pIDm, IDM_SIZE, &pRes[2], IDM_SIZE);

	return true;
}

/*
bSuccess = MyWriteWithoutEncryption(
pIDm,                       // Polling コマンドで取得した IDm IN
SERVICE_CODE_RW,            // サービスコード IN
pBlockNum[iBlockCount],     // ブロック番号 IN
pDataR);                    // 読み出したデータの格納場所 OUT
*/

bool Felica::MyWriteWithoutEncryption(unsigned char *pIDm,
	unsigned short usServiceCode,
	unsigned char ucBlockOffset,
	unsigned char *pDataW) {
	bool bSuccess;

	// Write Without Encryption コマンドの組み立て
	unsigned char pCom[] = {
		0x00,                                           // データ長
		0x08,                                           // コマンドコード
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IDm
		0x01,                                           // サービス数
		0x00, 0x00,                                     // サービスコード
		0x01,                                           // ブロック数
		0x00, 0x00, 0x00,                               // ブロック
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 任意のデータ
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // 任意のデータ
	};

	unsigned char pRes[512];

	unsigned short usComLen = sizeof(pCom);
	unsigned short usResLen = sizeof(pRes);

	pCom[0] = sizeof(pCom);

	memcpy_s(&pCom[2], sizeof(pCom) - 2, pIDm, IDM_SIZE);

	pCom[11] = usServiceCode & 0xFF;
	pCom[12] = usServiceCode >> 8;

	pCom[15] = ucBlockOffset;

	memcpy_s(&pCom[17], sizeof(pCom) - 17, pDataW, BLOCK_SIZE);

	// コマンドの発行
	bSuccess = felicalib_nfc_thru(pCom, usComLen, pRes, &usResLen);
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_thru(write without encryption)");
		return false;
	}

	// レスポンスのチェック
	if (usResLen != 1 + 1 + 8 + 1 + 1)
	{
		printf("write w/o enc のレスポンス長 %d が不正 #1\n", usResLen);
		return false;
	}
	if (pRes[0] != 1 + 1 + 8 + 1 + 1)
	{
		printf("write w/o enc のレスポンス長 %d が不正 #2\n", pRes[0]);
		return false;
	}
	if (pRes[1] != 0x09)
	{
		printf("write w/o enc のレスポンスコード %d が不正\n", pRes[1]);
		return false;
	}
	if (pRes[10] != 0x00)
	{
		printf("write w/o enc のステータスフラグが [%02x, %02x] のエラー\n", pRes[10], pRes[11]);
		return false;
	}

	return true;
}

bool Felica::MyReadWithoutEncryption(unsigned char *pIDm,
	unsigned short usServiceCode,
	unsigned char ucBlockOffset,
	unsigned char *pDataR) {
	bool bSuccess;

	// Read Without Encryption コマンドの組み立て
	unsigned char pCom[] = {
		0x00,                                           // データ長
		0x06,                                           // コマンドコード
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IDm
		0x01,                                           // サービス数
		0x00, 0x00,                                     // サービスコード
		0x01,                                           // ブロック数
		0x00, 0x00, 0x00                                // ブロック
	};

	unsigned char pRes[512];

	unsigned short usComLen = sizeof(pCom);
	unsigned short usResLen = sizeof(pRes);

	pCom[0] = sizeof(pCom);

	memcpy_s(&pCom[2], sizeof(pCom) - 2, pIDm, IDM_SIZE);

	pCom[11] = usServiceCode & 0xFF;
	pCom[12] = usServiceCode >> 8;

	pCom[15] = ucBlockOffset;

	// コマンドの発行
	bSuccess = felicalib_nfc_thru(pCom, usComLen, pRes, &usResLen);
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_thru(read without encryption)");
		return false;
	}

	// レスポンスのチェック
	if (usResLen < 1 + 1 + 8 + 1 + 1)
	{
		printf("read w/o enc のレスポンス長 %d が不正 #1\n", usResLen);
		return false;
	}
	if (pRes[0] < 1 + 1 + 8 + 1 + 1)
	{
		printf("read w/o enc のレスポンス長 %d が不正 #2\n", pRes[0]);
		return false;
	}
	if (pRes[1] != 0x07)
	{
		printf("read w/o enc のレスポンスコード %d が不正\n", pRes[1]);
		return false;
	}
	if (pRes[10] != 0x00)
	{
		printf("read w/o enc のステータスフラグが [%02x, %02x] のエラー\n", pRes[10], pRes[11]);
		return false;
	}

	// 読み出したデータのコピー
	memcpy_s(pDataR, BLOCK_SIZE, &pRes[13], BLOCK_SIZE);

	return true;
}

unsigned char* Felica::FelicaRW_main(int block, unsigned char* wData, bool read)
{
	//strcpy(po, "IDm = ");

	bool bSuccess;
	unsigned char pIDm[IDM_SIZE];

	// ライブラリの初期化
	bSuccess = felicalib_nfc_initialize();
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_initialize");
		goto L_End;
	}

	// ポートのオープン
	char szPort[16] = "";
	bSuccess = felicalib_nfc_open(szPort);
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_open");
		goto L_Uninitialize;
	}
	printf("port=%s\n", szPort);

	// デバイスの補足とアクセス権の取得
	DEVICE_INFO *pInfo;
	bSuccess = felicalib_nfc_poll_and_start_dev_access(
		DEVICE_TYPE_NFC_18092_212K      // FeliCa 212kbps
		| DEVICE_TYPE_NFC_18092_424K,   // FeliCa 424kbps
		3000,                           // タイムアウト ms
		&pInfo);
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_poll_and_start_dev_access");
		goto L_Close;
	}

	// IDm の表示
	DEVICE_DATA_NFC_18092_212_424K *pf;
	pf = (DEVICE_DATA_NFC_18092_212_424K *)pInfo->dev_info;
	printf("IDm=");
	MyPrintHex(pf->NFCID2, IDM_SIZE);

	// Polling コマンド発行
	bSuccess = MyPolling(SYSTEM_CODE, pIDm);
	if (!bSuccess)
	{
		goto L_StopDevAccess;
	}

	int pBlockNum[] =
	{
		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
		0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
		0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
		0x88,
	};
	char *pBlockName[] =
	{
		"S_PAD0", "S_PAD1", "S_PAD2", "S_PAD3", "S_PAD4", "S_PAD5", "S_PAD6", "S_PAD7",
		"S_PAD8", "S_PAD9", "S_PAD10", "S_PAD11", "S_PAD12", "S_PAD13", "REG",
		"ID", "D_ID", "SER_C", "SYS_C", "CKV", "CK",
		"MC",
	};

	if (true == read)
	{
		bSuccess = MyReadWithoutEncryption(
			pIDm,                       // Polling コマンドで取得した IDm
			SERVICE_CODE_RO,            // サービスコード
			pBlockNum[0],     // ブロック番号
			account);                    // 読み出したデータの格納場所

		bSuccess = MyReadWithoutEncryption(
			pIDm,                       // Polling コマンドで取得した IDm
			SERVICE_CODE_RO,            // サービスコード
			pBlockNum[1],     // ブロック番号
			domain);                    // 読み出したデータの格納場所

		bSuccess = MyReadWithoutEncryption(
			pIDm,                       // Polling コマンドで取得した IDm
			SERVICE_CODE_RO,            // サービスコード
			pBlockNum[2],     // ブロック番号
			user_id);                    // 読み出したデータの格納場所

		bSuccess = MyReadWithoutEncryption(
			pIDm,                       // Polling コマンドで取得した IDm
			SERVICE_CODE_RO,            // サービスコード
			pBlockNum[3],     // ブロック番号
			password);                    // 読み出したデータの格納場所

		bSuccess = MyReadWithoutEncryption(
			pIDm,                       // Polling コマンドで取得した IDm
			SERVICE_CODE_RO,            // サービスコード
			pBlockNum[4],     // ブロック番号
			name);                    // 読み出したデータの格納場所

		SetData();
	}
	else
	{
		unsigned char pDataW[16] =
		{
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
		};

		MyWriteWithoutEncryption(
			pIDm,
			SERVICE_CODE_RW,		    	//usServiceCode,
			block,							//ucBlockOffset,
			wData);
	}

L_StopDevAccess:
	// デバイス使用権の解放
	bSuccess = felicalib_nfc_stop_dev_access(
		RE_NOTIFICATION_SAME_DEVICE);
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_stop_dev_access");
	}

	// デバイス捕捉処理の停止
	bSuccess = felicalib_nfc_stop_poll_mode();
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_stop_poll_mode");
	}

L_Close:
	// ポートのクローズ
	bSuccess = felicalib_nfc_close();
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_close");
	}

L_Uninitialize:
	// ライブラリの終了処理
	bSuccess = felicalib_nfc_uninitialize();
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_uninitialize");
	}

L_End:

	return 0;
}


DEFファイルMyFelicaLiteS.def

LIBRARY

EXPORTS

FelicaRW_main @1
GetData @2
GetName @3


【手順】:
1.Visual Studioで、win32のプロジェクトを作ります。



2.空のライブラリを選択します。
アプリケーションの種類で、"DLL"を選択し、追加のオプションで"空のプロジェクト"をチェックしてください。


3.ファイルを作り編集する。

ヘッダーファイル(FelicaRW.h)、
インプリメンテーションファイル(FelicaRW.cpp)、
DEFファイル(MyFelicaLiteS.def)

を追加します。
名前は、任意ですが、以後統一名称として話を進めますので、異なる名称を付けた場合には読み替えてください。

4.ビルドして、DLL形式のライブラリファイルを作ってください。

【詳細】:

1.コーディング解説

実装FelicaMail.aspx.cs

void Felica::FelicaRW_main(int block, unsigned char* wData, bool read)
{
	//strcpy(po, "IDm = ");

	bool bSuccess;
	unsigned char pIDm[IDM_SIZE];

	// ライブラリの初期化
	bSuccess = felicalib_nfc_initialize();
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_initialize");
		goto L_End;
	}

	// ポートのオープン
	char szPort[16] = "";
	bSuccess = felicalib_nfc_open(szPort);
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_open");
		goto L_Uninitialize;
	}
	printf("port=%s\n", szPort);

	// デバイスの補足とアクセス権の取得
	DEVICE_INFO *pInfo;
	bSuccess = felicalib_nfc_poll_and_start_dev_access(
		DEVICE_TYPE_NFC_18092_212K      // FeliCa 212kbps
		| DEVICE_TYPE_NFC_18092_424K,   // FeliCa 424kbps
		3000,                           // タイムアウト ms
		&pInfo);
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_poll_and_start_dev_access");
		goto L_Close;
	}

	// IDm の表示
	DEVICE_DATA_NFC_18092_212_424K *pf;
	pf = (DEVICE_DATA_NFC_18092_212_424K *)pInfo->dev_info;
	printf("IDm=");
	MyPrintHex(pf->NFCID2, IDM_SIZE);

	// Polling コマンド発行
	bSuccess = MyPolling(SYSTEM_CODE, pIDm);
	if (!bSuccess)
	{
		goto L_StopDevAccess;
	}

	int pBlockNum[] =
	{
		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
		0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
		0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
		0x88,
	};
	char *pBlockName[] =
	{
		"S_PAD0", "S_PAD1", "S_PAD2", "S_PAD3", "S_PAD4", "S_PAD5", "S_PAD6", "S_PAD7",
		"S_PAD8", "S_PAD9", "S_PAD10", "S_PAD11", "S_PAD12", "S_PAD13", "REG",
		"ID", "D_ID", "SER_C", "SYS_C", "CKV", "CK",
		"MC",
	};

	if (true == read)
	{
		bSuccess = MyReadWithoutEncryption(
			pIDm,                       // Polling コマンドで取得した IDm
			SERVICE_CODE_RO,            // サービスコード
			pBlockNum[0],     // ブロック番号
			account);                    // 読み出したデータの格納場所

		bSuccess = MyReadWithoutEncryption(
			pIDm,                       // Polling コマンドで取得した IDm
			SERVICE_CODE_RO,            // サービスコード
			pBlockNum[1],     // ブロック番号
			domain);                    // 読み出したデータの格納場所

		bSuccess = MyReadWithoutEncryption(
			pIDm,                       // Polling コマンドで取得した IDm
			SERVICE_CODE_RO,            // サービスコード
			pBlockNum[2],     // ブロック番号
			user_id);                    // 読み出したデータの格納場所

		bSuccess = MyReadWithoutEncryption(
			pIDm,                       // Polling コマンドで取得した IDm
			SERVICE_CODE_RO,            // サービスコード
			pBlockNum[3],     // ブロック番号
			password);                    // 読み出したデータの格納場所

		bSuccess = MyReadWithoutEncryption(
			pIDm,                       // Polling コマンドで取得した IDm
			SERVICE_CODE_RO,            // サービスコード
			pBlockNum[4],     // ブロック番号
			name);                    // 読み出したデータの格納場所

		SetData();
	}
	else
	{
		unsigned char pDataW[16] =
		{
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
		};

		MyWriteWithoutEncryption(
			pIDm,
			SERVICE_CODE_RW,		    	//usServiceCode,
			block,							//ucBlockOffset,
			wData);
	}

L_StopDevAccess:
	// デバイス使用権の解放
	bSuccess = felicalib_nfc_stop_dev_access(
		RE_NOTIFICATION_SAME_DEVICE);
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_stop_dev_access");
	}

	// デバイス捕捉処理の停止
	bSuccess = felicalib_nfc_stop_poll_mode();
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_stop_poll_mode");
	}

L_Close:
	// ポートのクローズ
	bSuccess = felicalib_nfc_close();
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_close");
	}

L_Uninitialize:
	// ライブラリの終了処理
	bSuccess = felicalib_nfc_uninitialize();
	if (!bSuccess)
	{
		PrintNfcLastErr("felicalib_nfc_uninitialize");
	}

L_End:

	return 0;
}

FelicaRW_main(int block, unsigned char* wData, bool read)の引数リストは以下のようになっています。
int block … 読み込み、又は書き込みをするブロックを指定します。
char* wData … 書き込みをする場合、Felicaに書き込みたい文字列を指定します。
bool read … 読み込みをする場合にはtrueを、書き込みをする場合にはfalseを指定します。


上図は、Felica Lite-Sのブロックイメージです。ユーザ定義で自由にデザインできますが、今回はこのように定義しました。

  1. 0x00ブロック---メールアカウント部
  2. 0x01ブロック---メールドメイン部
  3. 0x02ブロック---ユーザID
  4. 0x03ブロック---パスワード
  5. 0x04ブロック---ユーザネーム
いづれのブロックも最大長が16バイトで、特に注意すべき点は、ユーザネーム。
ユーザネームは、マルチバイト(Unicode)でエンコードします。
次工程のトリガー編で、このライブラリを利用するアプリケーションがポインタを受け取るときにUnicodeとして引き取るので、
漢字やひらがな等の日本語をUnicodeで書き込むことを覚えておいてください。


unsigned char* Felica::GetName()
{
	return name;
}

unsigned char* Felica::GetData()
{
	return str;
}
上記で説明した通り、ライブラリを使用するアプリケーション側のポインタ文字列の受け方が異なるため、 ゲッターを二つに分けて利用します。

以上となります。