ArduinoでFelicaリーダー2014年11月14日

FelicaカードのIDmと残高表示

ArduinoボードにはATmega32U2を使用したArduino Leonardo互換ボードを使用しています。USB内蔵タイプはシリアルポートが空いているのでスケッチの更新のたびにRC-S620/Sを外さなくて済みますし、読み取ったIDmやカード・残高をキー入力としてPCに送ることも出来ます。

  • コントローラ
    ATmega32U2 Arduino Leonardoクローン
     Caterina-Minimus.hex
  • Felicaリーダー RC-S620/S
    ソニーのFelicaリーダー。Arduino向けRC-S620/S制御ライブラリを使用しました。
    USB内蔵タイプのMPUはシリアルポートが異なりますのでRCS620S.cppを直接修正しました。

【プログラム】
「FeliCa電子マネー残高照会機モドキ」のブログで紹介されています。残高照会の部分はそののまま使用させていただきました。FelicaカードのHackはこのサイトで紹介されています。IDmの読み出しの部分はRC-S620/Sライブラリーのサンプルプログラムを使用しました。
デジタルピン5、6にそれぞれスイッチを接続しています。ピン5はUSBキー出力のON/OFFスイッチで、ピン6はIDm読み出し/残高照会の切り替えスイッチです。

Felica reader
// Felica caed reader             2014/11/14
//  Read out the card history by RC-S620/S.
//  Corresponding to PASSNET, Edy, nanaco and waon.
//  MPU: ATmega32U2 + Caterina-Minimus.hex
// Sample program site.
// http://www.orsx.net/blog/archives/3835
// Felica card informations.
// http://sourceforge.jp/projects/felicalib/wiki/FrontPage
// 
#include <LiquidCrystal.h>
#include <RCS620S.h>
 
// RCS620S
#define COMMAND_TIMEOUT               400
#define POLLING_INTERVAL              500
#define RCS620S_MAX_CARD_RESPONSE_LEN 30
 
// FeliCa Service/System Code
#define CYBERNE_SYSTEM_CODE           0x0003
#define COMMON_SYSTEM_CODE            0xFE00
#define PASSNET_SERVICE_CODE          0x090F
#define EDY_SERVICE_CODE              0x170F
#define NANACO_SERVICE_CODE           0x564F
#define WAON_SERVICE_CODE             0x680B
 
RCS620S rcs620s;

// SW, LED and LCD pin assign.
#define LED_PIN 7
#define SEL_PIN 6
#define KEY_PIN 5
//   1602         RS R/W  E  D4  D5  D6  D7
LiquidCrystal LCD(0, 1, 2, 15, 16, 17, 18);

void setup(){
  pinMode(SEL_PIN, INPUT);    // for select SW
  pinMode(KEY_PIN, INPUT);    // for output keybord SW
  digitalWrite(SEL_PIN, HIGH);// Pullup 
  digitalWrite(KEY_PIN, HIGH);// Pullup 
  pinMode(LED_PIN, OUTPUT);   // for Polling Status
  digitalWrite(LED_PIN, LOW); 
  Keyboard.begin();

  LCD.begin(16, 2);
  LCD.clear();
  LCD.setCursor(0, 0);
  LCD.print("Felica reader");
  LCD.setCursor(0, 1);
  LCD.print("Start...");
  delay(3000);

  // Modified the library for ATmega32U2.
  //  Serial -> Serial1(RX:8, TX:9 pins)
  Serial1.begin(115200);  
  int ret = rcs620s.initDevice();
  while (!ret) {
    delay(POLLING_INTERVAL);
    ret = rcs620s.initDevice();
  }
}
 
void loop(){
  digitalWrite(LED_PIN, HIGH);
  if(digitalRead(SEL_PIN)){
    readhistory();
  } else {
    rcs620s.timeout = COMMAND_TIMEOUT;
    LCD.clear();
    if(rcs620s.polling()) {
      LCD.print("IDm:");
      LCD.setCursor(0, 1);
      for(int i = 0; i < 8; i++) {
        if(rcs620s.idm[i] / 0x10 == 0) LCD.print(0);
        LCD.print(rcs620s.idm[i], HEX);
        if(digitalRead(KEY_PIN)) {
          sprintf(result, "%02x", rcs620s.idm[i]);
          Keyboard.print(result); 
        }
      }
      if(digitalRead(KEY_PIN)) Keyboard.println(); 
    } else {
    LCD.setCursor(0, 0);
    LCD.print("Reading IDm.");
    if(digitalRead(KEY_PIN)) {
        LCD.setCursor(15, 0);
        LCD.write(0x7E);
    }
    LCD.setCursor(0, 1);
    LCD.print("Polling...");
    }
  } 
  rcs620s.rfOff();
  digitalWrite(LED_PIN, LOW);
  delay(POLLING_INTERVAL);
}

void readhistory() {
  uint32_t balance;
  uint8_t buf[RCS620S_MAX_CARD_RESPONSE_LEN];
   
  rcs620s.timeout = COMMAND_TIMEOUT;
   
  // サイバネ領域
  if(rcs620s.polling(CYBERNE_SYSTEM_CODE)){
    // Suica PASMO
    if(requestService(PASSNET_SERVICE_CODE)){
      if(readEncryption(PASSNET_SERVICE_CODE, 0, buf)){
        // Little Endianで入っているPASSNETの残高を取り出す
        balance = buf[23];                  // 11 byte目
        balance = (balance << 8) + buf[22]; // 10 byte目
        // 残高表示
        printBalanceLCD("PASSNET", &balance);
      }
    }
  }
   
  // 共通領域
  else if(rcs620s.polling(COMMON_SYSTEM_CODE)){
    // Edy
    if(requestService(EDY_SERVICE_CODE)){
      if(readEncryption(EDY_SERVICE_CODE, 0, buf)){
        // Big Endianで入っているEdyの残高を取り出す
        balance = buf[26];                  // 14 byte目
        balance = (balance << 8) + buf[27]; // 15 byte目
        // 残高表示
        printBalanceLCD("Edy", &balance);
      }
    }
     
    // nanaco
    if(requestService(NANACO_SERVICE_CODE)){
      if(readEncryption(NANACO_SERVICE_CODE, 0, buf)){
        // Big Endianで入っているNanacoの残高を取り出す
        balance = buf[17];                  // 5 byte目
        balance = (balance << 8) + buf[18]; // 6 byte目
        balance = (balance << 8) + buf[19]; // 7 byte目
        balance = (balance << 8) + buf[20]; // 8 byte目
        // 残高表示
        printBalanceLCD("nanaco", &balance);
      }
    }
     
    // waon
    if(requestService(WAON_SERVICE_CODE)){
        // Block number History1=1, History2=3, History3=5
      if(readEncryption(WAON_SERVICE_CODE, 5, buf)){
        // Big Endianで入っているWaonの残高を取り出す
        balance = buf[17];                  // 21 byte目
        balance = (balance << 8) + buf[18]; // 22 byte目
        balance = (balance << 8) + buf[19]; // 23 byte目
        balance = balance & 0x7FFFE0;       // 残高18bit分のみ論理積で取り出す
        balance = balance >> 5;             // 5bit分ビットシフト
        // 残高表示
        printBalanceLCD("waon", &balance);
      }
    }
  } else {
    LCD.clear();
    LCD.setCursor(0, 0);
    LCD.print("Reading History");
    if(digitalRead(KEY_PIN)) {
        LCD.setCursor(15, 0);
        LCD.write(0x7E);
    }
    LCD.setCursor(0, 1);
    LCD.print("Polling...");
  }
}
 
// request service
int requestService(uint16_t serviceCode){
  int ret;
  uint8_t buf[RCS620S_MAX_CARD_RESPONSE_LEN];
  uint8_t responseLen = 0;
   
  buf[0] = 0x02;
  memcpy(buf + 1, rcs620s.idm, 8);
  buf[9] = 0x01;
  buf[10] = (uint8_t)((serviceCode >> 0) & 0xff);
  buf[11] = (uint8_t)((serviceCode >> 8) & 0xff);
 
  ret = rcs620s.cardCommand(buf, 12, buf, &responseLen);
   
  if(!ret || (responseLen != 12) || (buf[0] != 0x03) ||
      (memcmp(buf + 1, rcs620s.idm, 8) != 0) || ((buf[10] == 0xff) && (buf[11] == 0xff))) {
    return 0;
  }
 
  return 1;
}
 
int readEncryption(uint16_t serviceCode, uint8_t blockNumber, uint8_t *buf){
  int ret;
  uint8_t responseLen = 0;
   
  buf[0] = 0x06;
  memcpy(buf + 1, rcs620s.idm, 8);
  buf[9] = 0x01; // サービス数
  buf[10] = (uint8_t)((serviceCode >> 0) & 0xff);
  buf[11] = (uint8_t)((serviceCode >> 8) & 0xff);
  buf[12] = 0x01; // ブロック数
  buf[13] = 0x80;
  buf[14] = blockNumber;
 
  ret = rcs620s.cardCommand(buf, 15, buf, &responseLen);
 
  if (!ret || (responseLen != 28) || (buf[0] != 0x07) ||
      (memcmp(buf + 1, rcs620s.idm, 8) != 0)) {
    return 0;
  }
 
  return 1;
}
 
void printBalanceLCD(char *card_name, uint32_t *balance){
  char result[17];
  sprintf(result, "Result = \\%u", *balance);
  LCD.clear();
  LCD.setCursor(0, 0);
  LCD.print(card_name);
  LCD.setCursor(0, 1);
  LCD.print(result);

  sprintf(result, "%s - %u", card_name, *balance);
  if(digitalRead(KEY_PIN)) Keyboard.println(result); 
  return;
}

使用したArduino IDEのバージョンは1.0.2 ATmega32U2対応版です。
ソースプログラム中のカギ括弧にご注意ください。HTMLのタグとの区別ため一部に全角文字を使用しています。Arduino IDEの置換え機能で半角に変換してからコンパルしてください。

コメント

トラックバック

このエントリのトラックバックURL: http://azss.asablo.jp/blog/2014/11/14/7491689/tb

※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。