2019년 3월 21일 목요일

NFC를 이용한 선불 교통 카드 잔액 조회기

1. 딸래미(王)의 귀환

 아직 초등학생인 딸래미가 있다. 집에서 먼(대중교통으로 40분) 학원을 다니게 되었는데, 셔틀도 없다. 학원에 갈때는 와이프가 데려다 주지만, 올 때는 내가 퇴근하면서 학원으로 가서 딸래미를 데리고 집으로 온다. 하지만 나도 직딩이라 항상 갈 수는 없는 상황이었다.  딸래미의 귀환이 걱정되기도 하고, 만일의 사태를 위하여 딸래미에게 선불 교통 카드를 사 주었다.

[ 하트빵빵 귀여운 토끼 버전의 교통카드 ]
한두번 사용해보게 했지만, 잔액을 기억 못하는 상황이라, 집에서도 손쉽게 잔액이 확인 가능하도록 리더를 만들어야 했다.

2. 모듈의 귀환

 이번에 제작을 위하여 이전에 사용한 모듈들을 귀환시켰다.  예전의 포스트에 사용한 PN532 모듈을 제일 먼저 귀환 시키고,

[ PN532를 소개한 포스트 바로 가기 ]

[ PN532 ] 

그 다음 OLED 및 ESP8266을 귀환 시켯다.
[ OLED + ESP8266을 이용한 오실로스코프 바로가기 ]


[ OLED ]


3. 제작 결과물 소개

 일단 결과물을 소개하면 아래의 이미지와 같다.
[ 나름 투명 아크릴 상판 ]

[ 측면에서 찰칵. 이번엔 배터리 없이 설계 ]

[ 전원을 연결하면 카드를 태깅하라고 궁시렁 ]

[ 좀더 신선하게 고속버스 안에서 촬영 ]

[ 카드를 가져다 대면 잔액이 표시됨 ]


수준이 좀 낮은 상태로 촬영 되었지만, 동작은 아래의 모습으로 진행된다.




4. 제작 방법 소개

납땜할 포인트가 별로 없다. 기껏해야 20-30 포인트 정도된다.  회로도 까지라고 말할 필요도 없고 아래와 같이 연결하면 된다.
[ 심플한 연결도~ ]

나는 모듈들은 모두 소켓 방식을 사용하기 때문에, ESP8266은 기판의 뒷면에 꼽을수 있는 형태로 제작되었다. NFC 나 OLED 모듈은 기판의 앞면에서 꼽는다.
   
[ 급제작으로 인한 케이블 배선의 엉성함은 참아 주시라~ ]

소스코드는 전체 공개이지만, 라이브러리는 이 포스트에 기록하지 않는다.
먼저 필요한 라이브러리는 2개이다.

PN532관련된 라이브러리는 아래의 링크에서 다운로드 가능하다.
https://github.com/adafruit/Adafruit-PN532


OLED에 한글을 출력하는 과정은 나름 껄끄롭지만, 그 전체 과정 및 라이브러리는 아래의 링크에서 다운로드 가능하다.
http://andy-power.blogspot.com/2018/08/oled.html

대략 이번 선불 교통카드 잔액 조회 기능을 만들기 위해서는 100행 정도를 코딩했으며, 코딩된 전체 내용은 아래와 같다.
/* ------------------------------------------------------
  T-Money Checker
  +-------- GPL License --------------------------------+
  do not remove contact & url
  contact :  terminal0070@gmail.com
  blog :  https://andy-power.blogspot.com/
  -------------------------------------------------------- */
#include <Arduino.h>
#include "Adafruit_PN532.h"
#include "Display_SSD1306.h"
#include "HanDraw.h"
#define OLED_RESET -1 //  S/W 리셋..사용하지 않음

// PN532 V3 SPI모드로 사용하기 위해서는 모듈의 스위치를 변경하고 VCC,GND 포함하여
// 6개의 연결이 필요하다.
// ESP8266 기준으로 정의
#define PN532_SCK  (D5) //
#define PN532_MOSI (D7) //
#define PN532_SS   (D3) //
#define PN532_MISO (D6) //

// I2C 모드를 이용하기 위해서는 아래 2 연결이 필요하다.
#define PN532_IRQ   (2// Not Use
#define PN532_RESET (0// Not connected by default on the NFC Shield

//--------------------------------------------------------------
// Global variables
//--------------------------------------------------------------
Display_SSD1306 Oled(OLED_RESET); // 화면 표시용 객체
Adafruit_PN532 nfc(PN532_SS);     // NFC 객체

//--------------------------------------------------------------
// Setup function
//--------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(50);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH); // 내장 LED 끄고
  Serial.println("Start NFC");

  nfc.begin();
  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Didn't find PN53x board");
    while (1); // PN532 못찾은 경우에는 다시 부팅해 버린다.!! (찾을때 까지 리부팅)
  }
  // 제대로 연결되었으면 정보를 출력한다.
  Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX);
  Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC);
  Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC);

  // Oled I2C 방식으로 연결하고, 주소는 0x3c
  Oled.begin(SSD1306_SWITCHCAPVCC, 0x3C, false);

  // Callback 함수를 설정해 주면 필요시 호출 하여 사용함.
  HanDraw.begin(12,  
    [](void) { Oled.clearDisplay(); },// 화면 지우는 콜백함수
    [](int16_t x, int16_t y, uint16_t color) {// 1 픽셀을 그리는 콜백 함수
      Oled.drawPixel(x, y, color);   },
    [](void) { Oled.display(); } //  메모리에서 Display 표시 버퍼까지 보내는 함수
   );

  // configure board to read RFID tags
  nfc.SAMConfig();
  Serial.println("Waiting for an ISO14443A Card ...");
  HanDraw.display(); // 사실상 Oled.display() 동일....
  delay(2000);

  HanDraw.clear();
  HanDraw.setFontSize(16);
  HanDraw.drawString(1, 13, "선불 교통 카드를");
  HanDraw.drawString(1, 33, "태그해 주세요");
  HanDraw.display(); // 사실상 Oled.display() 동일...
}

//--------------------------------------------------------------
// loop function
//--------------------------------------------------------------
void loop() {
  uint8_t success;
  uint8_t responseLength = 64;
  success = nfc.inListPassiveTarget();
  if (success) {
    Serial.println("Found something!");
    // T-money 카드번호, 날짜등을 읽기 위한것
    // OLED에는 표시하지 않음
    uint8_t cardInfo[responseLength];
    uint8_t cardNumSize = 0;
    uint8_t selectApdu[] = { 0x00, 0xA4, 0x00, 0x00, 0x02, 0x42, 0x00 };
    success = nfc.inDataExchange(selectApdu, sizeof(selectApdu), cardInfo, &responseLength);
    if (success) {
      Serial.print("responseLength: "); Serial.println(responseLength);
      nfc.PrintHexChar(cardInfo, responseLength);
      if (responseLength >= 24) {
        Serial.print("card number : "); nfc.PrintHexChar(cardInfo + 8, 8);
        Serial.print("card date : ");   nfc.PrintHexChar(cardInfo + 21, 4);
      }
    }

    // 잔고 조회용
    // 조회가 완료되면 OLED 표시
    uint8_t balance[responseLength];
    uint8_t balanceApdu[] = { 0x90, 0x4C, 0x00, 0x00, 0x04 } ;
    success = nfc.inDataExchange(balanceApdu, sizeof(balanceApdu), balance, &responseLength);
    if (success) {
      Serial.print("responseLength: "); Serial.println(responseLength);
      nfc.PrintHexChar(balance, responseLength);
      if (responseLength >= 4) {
        char fpsbuf[32] = ""; // 숫자를 문자열로 바꾸어 화면에 출력할때 사용됨
        uint32_t money = balance[0] * 256 * 256 * 256 +  balance[1] * 256 * 256 +  balance[2] * 256 + balance[3];
        HanDraw.clear();
        HanDraw.drawString(2, 7, " 교통 카드 잔액 ");
        HanDraw.drawString(2, 25, "------------------");
        dtostrf((float)money, 10, 0,   fpsbuf);
        HanDraw.drawString(2, 43, fpsbuf);
        HanDraw.display();
        delay(2000);
        Serial.print("money : "); Serial.println(money);
      }
    }
  }
  else {
    HanDraw.clear();
    HanDraw.drawString(1, 13, "선불 교통 카드를");
    HanDraw.drawString(1, 33, "태그해 주세요");
    HanDraw.display(); // 사실상 Oled.display() 동일...
  }
  delay(100);
}


-2024-03-11 추가
위의 코드는 일반적인 선불 교통카드에 대한 조회 기능이다. 휴대폰의 NFC 교통카드 번호 확인은 위의 코드로 불가능하다.  nsmaster 님이 알려주신 아래의 APDU로 휴대폰의 교통카드 번호가 확인하다고 댓글을 달아 주셨다. (감사합니다.)
{0x00, 0xA4, 0x04, 0x00, 0x07, 0xD4, 0x10, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00} 
집안에 안드로이드폰이 없어서 아직 확인은 못했다. 

5. 끝으로

 사실 일전에 다 연구한 모듈과 내용을 다시금 정리하고 디스플레이를 추가한 결과물이다. 조회기만을 만드는 것은 당일 저녁 딸래미를 학원에서 집까지 데려오고 나서 시작해서 그날 잠자기 전에 다 만들었다. (수 시간 정도 걸림)  예전에 NFC 모듈 연구 당시 선불교통카드에 APDU 방식으로 커멘드를 보내고 받아야 하는데, 보내야하는 커멘드를 어느 자료에서도 찾을 수가 없어서 정말 고생했다.  이 포스트에서는 관련 커멘드는 1행의 소스코드로 공개되었지만, 이 1행의 코드는 수일간 고생해서 알아낸 코드이다.  연구는 끝이 없고 시간은 적으며, 흰머리는 너무나도 빠르게 늘어간다. ㅠㅠ


댓글 32개:

  1. 교통카드잔액조회기 제작에 대하여 논의드리고자 합니다.
    메일로 전화번호를 주시면 연락드리겠습니다.
    jaemsee@hotmail.com

    답글삭제
    답글
    1. 댓글에 적으신 이메일로 연락드렸습니다.~

      삭제
  2. 카드정보리더기 제작에 대하여 논의드리고자 합니다.
    메일로 전화번호를 주시면 연락드리겠습니다.
    감사합니다.
    mingo.kim@evar.co.kr

    답글삭제
    답글
    1. 이메일로 답변드렸습니다.

      삭제
    2. 감사합니다.
      저도 이메일로 답변드렸으니 확인부탁드립니다.

      삭제
  3. 님 블로그 보고 아들래미꺼 만들어 줬습니다. 감사합니다.

    답글삭제
  4. 안녕하세요 선생님 프로젝트 보고 정말 멋져서 답글답니다.
    다름이 아니라 제가 지금 고등학생인데 학교에서 동아리 활동중에 해당 프로젝트를 사용해도 될지 여쭤보려 연락드립니다. 그리고 혹시 궁금한점 생기면 연락드려도 될지 여쭤보려 연락드립니다...!

    답글삭제
    답글
    1. 어차피 웹에 공개한 것이기 때문에 사용하셔도 관계없습니다. 다만 젊은 친구 이시니 더 좋은 아이디어가 추가되길 기원합니다.

      삭제
  5. 질문이 있는데 대화 가능할까요? 가능하시다면 peter5157@naver.com으로 연락주시면 감사하겠습니다

    답글삭제
  6. 혹시 만드는 과정을 자세하게 설명 받을수 있을까요?? sin06004@naver.com

    답글삭제
    답글
    1. 회로도는 이미 본문에 포함되어 있긴 한데 자세한 설명은 무엇을 말씀하시는 것일까요?

      삭제
  7. 작성자가 댓글을 삭제했습니다.

    답글삭제
  8. 캐시비 카드는 읽을수가 없는데 혹시 왜 그런지 아시나요?? 혹시 이유를 아시면eundong2428@naver.com 으로 연락 주시면 감사합니다.

    답글삭제
    답글
    1. 소스코드내용중에 selectApdu 와 관계되었을지도 모르겠네요. 안타깝게도 캐시비카드를 이용해 본적이 없습니다. 카드 종류에 따라 Apdu를 다르게 사용하기는 합니다만, 일반적인 잔액조회 apdu가 되야 할듯 하긴합니다. 크게 도움을 드리지 못해서 죄송합니다.

      삭제
  9. 작성자가 댓글을 삭제했습니다.

    답글삭제
    답글
    1. 이곳에 질문해주시면 다른 분들도 궁금증이 해결 될 수 있습니다.
      가능하시다면 이곳에 문의 부탁 드립니다.

      삭제
  10. 안녕하세요 따라 만들어보려고 했는데 RFID 모듈을 rc522를 사용하려고하는데
    oled 디스플레이랑 rc522만으로는 이 글대로 만들수 없나요? 꼭 저 모듈들이 필요한건가요??

    답글삭제
    답글
    1. NFC 라이브러리만 제대로 사용하신다면 될 것 같습니다. 사실 교통카드 잔액 조회에서 핵심은 APDU일뿐, NFC 리더기의 종류는 관계없습니다.

      삭제
    2. 답변 감사합니다. 버스카드 잔액 인식이 가능한것만 보고싶어서 한글 출력은 필요하지 않을것같은데 그렇게될경우 코드를 어떻게 짜야할까요??

      삭제
    3. OLED 에 글자를 출력하기 위해서는 어떤 라이브러리이던 한개는 사용하셔야 할꺼에요. 이미 OLED는 공부하신듯 하니, 기존에 사용하시던 글자 출력용 라이브러리를 이용하셔도 됩니다.

      삭제
  11. 가장 핵심은 APDU입니다. 통신 방법입니다. NFC 통신 자체가 특정 API가 있는 것이 아니라 통신 방법에 의거하여 서로 정보를 주고 받는 형태입니다. NFC 카드쪽에 이미 정의된 바이트들을 보내면 거기에 해당하는 답변을 주는 형태입니다. 0x00, 0xA4, 0x00, 0x00, 0x02, 0x42, 0x00 와 같은 순서로 보내면 NFC 카드에서 번호를 줍니다.

    답글삭제
  12. 문의하신 "내부적으로 계산" 이부분은 큰 의미가 없습니다. 카드에 APDU를 보내면 응답으로 APDU가 오고, 그 응답에서 필요한 부분을 발췌해서 사용하는 형태입니다. 예를들어 카드 번호는 응답 받은 바이트들 중에서 9번쨰에서 부터 8 글자가 카드 번호가 됩니다.

    답글삭제
  13. 일반적인 NFC 통신 절차 입니다.
    1. 리더(Reader)가 무선으로 카드쪽에 자기장을 보냅니다.
    2. 카드는 자기장을 전기로 변환하여 On 상태가 됩니다.
    3. 카드와 리더 사이에 무선통신으로 연결합니다.
    4. 리더는 카드에 접근 가능한 암호화 키를 카드에 보냅니다.
    교통카드의 경우 이 암호화키는 공개되어 있습니다.
    5. 카드는 암호화 키를 해독하여 향후 모든 통신이 이 암호화 키를 사용하여 이루어질 수 있도록 합니다.
    6. 리더는 카드에 명령어를 보냅니다.
    7. 리더는 카드로 부터 응답 받은 내용을 처리합니다.
    필요시 6번과 7번 과정을 계속 진행합니다.

    답글삭제
    답글
    1. 안녕하세요, 4번항목의 "암호화 키"에대하여 공개되어있다고 하는데 해당내용이 KS X 6924-2 규격의 "select file"항목의 명령어 인 것 같습니다. 다만, Data Field Name의 경우 여기선 { 0x00, 0xA4, 0x00, 0x00, 0x02, 0x42, 0x00 };
      0x42 로 선택하는데 0x42에 대한 정보는 어디서 확인 가능할까요?
      추가적으로 모바일 티머니의 경우엔 어떤 것으로 설정해야 할지 ...확인가능하다면 알려주시면 감사하겠습니다!

      삭제
    2. 암호화 키라고 적었는데 사실상 read key 입니다. NFC 카드에는 아시겠지만 read key, write key가 존재하자나요. 본문의 끝부분에 select file 명령어는 알아내는데 1개월 정도 걸렸다고 적었습니다. 이 부분은 좀지난 버전의 안드로이드 티머니 앱을 구해서 디컴파일 해서 찾았습니다. 근데 디컴파일해도 제대로된 코드가 안나와서 애 먹었습니다.

      삭제
    3. 아~~ select file 명령어의 data field를 명시하는 다른 규격서가 존재하는지 궁금했습니다. 선생님께서 저 "0x42"를 알아내기위해 엄청 고생하셨을 것 같습니다... 고생해서 찾으신 내용을 이렇게 공개해주셔서 감사합니다.
      모바일 티머니의 경우 (ex. 휴대폰) 카드번호가 전부 00000000으로 나오길래 혹시 모바일도 읽는 방법이 있는가 해서 여쭤봤습니다 !
      일반 티머니카드는 select file 명령어 입력시 티머니 카드에 표기된 카드번호를 알 수 있는데, 모바일은 동작되는 원리가 조금 다른가봅니다...
      일반 티머니카드는 어떤 앱에서는 14443-4A로 나오고, 어디서는 Mifare Plus로 나오고... 이쪽내용이 상당히 햇깔리네요ㅠㅠ 하다가 추가 공유할만한 내용 있으면 공유하겠습니다. 감사합니다!

      삭제
    4. nsmaster님 건승을 기원합니다.

      삭제
    5. 안녕하세요 선생님, 공유할만한 내용인 것 같아서 공유드립니다. 기재되어있는 APDU로는 티머니는 읽을 수 있지만, 휴대폰 NFC는 못읽습니다.
      여러모로 찾아보고
      {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD4, 0x10, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00} 해당하는 APDU를 입력하면 휴대폰의 NFC 교통카드번호도 확인 가능합니다. 티머니 교통카드도 마찬가지로 잘 읽을 수 있으니 이 글을 읽는분들께 도움이되길 바라겠습니다.

      삭제
    6. 우와 감사합니다. 정말 도움이 될것 같습니다. !!

      삭제

3단 6핀 스위치로 DC 모터의 회전 방향을 바꾸어 보자

1. 필요는 연구의 어머니 항상 느끼는 부분이다. 필요하지 않으면 연구하지 않으며, 필요하면 연구한다. DC 모터를 조건에 따라서 정방향 또는 역방향으로 회전시켜야 하는 필요가 생겼다. 처음에는 MCU 및 Relay Switch를 이용하는 방법을 생각...