NFC 권한설정

개발/Android 2024. 10. 21. 11:50
반응형

NFC 태그 사용을 위한 권한설정에 대해 알아보겠다.

NFC 태그가 앱에 의해 감지될 때 android.nfc.action.TAG_DISCOVERED는 NFC 태그가 감지될 때 인텐트를 전달하는 데 필요하다. 이를 위해 AndroidManifest.xml에 설정을 추가해야 한다. 이 설정은 TAG_DISCOVERED, TECH_DISCOVERED, NDEF_DISCOVERED와 같은 액션을 통해 NFC 태그 감지 시 앱으로 인텐트를 전달하는 역할을 한다.

 

권한 설명

  1. android.nfc.action.NDEF_DISCOVERED:
    NDEF 메시지를 포함한 NFC 태그가 감지되었을 때 작동하는 액션이다. 주로 특정 애플리케이션 데이터 또는 MIME 타입이 있는 태그에서 사용된다.
  2. android.nfc.action.TAG_DISCOVERED:
    NFC 태그가 감지되었을 때 작동하는 기본 액션으로, NDEF 메시지가 포함되지 않은 모든 NFC 태그를 처리할 수 있다.
  3. android.nfc.action.TECH_DISCOVERED:
    태그가 특정 기술(예: NfcA, MifareUltralight 등)을 지원할 때 작동한다. 이를 통해 특정 기술 스택을 가진 태그를 처리할 수 있다. 예를 들어 nfc_tech_filter.xml에서 정의된 기술과 일치하는 태그가 감지되었을 때 인텐트를 처리한다.

추가 설명

  • MIME 타입은 NDEF 태그의 데이터를 처리할 때 필수입니다. 예를 들어, "text/plain"은 특정 애플리케이션 데이터 타입을 지정한 것입니다.
  • NDEF_DISCOVERED는 NDEF 메시지가 포함된 태그만 처리할 수 있으므로, 태그의 내용이 특정 유형(MIME 타입)일 때만 작동하게끔 설정됩니다.
  • TECH_DISCOVERED는 기술 스택을 기반으로 태그를 처리하며, nfc_tech_filter.xml에서 정의한 기술과 일치하는 태그가 감지되면 작동합니다.

AndroidManifest.xml 에 추가된 설정 예 :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mwkg.testwebview">


    <!-- NFC 권한 설정 -->
    <uses-permission android:name="android.permission.NFC"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.TestWebView">

        <!-- FCM 푸시 알림 서비스 등록 -->
        <service
            android:name=".MyFirebaseMessagingService"
            android:exported="true"
            android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.TestWebView">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <!-- NFC NDEF 태그 인텐트 필터 -->
            <intent-filter>
                <!-- NDEF 메시지를 포함한 NFC 태그가 감지될 때 앱으로 인텐트를 전달 -->
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />

                <!-- NDEF 메시지에서 처리할 MIME 유형 지정 (예: 특정 애플리케이션 또는 텍스트 MIME 유형) -->
                <data android:mimeType="text/plain" />  <!-- ex: "text/plain" 또는 "application/vnd.*" -->
            </intent-filter>

            <!-- NFC 태그 인텐트 필터 -->
            <intent-filter>
                <!-- 모든 NFC 태그가 감지될 때 앱으로 인텐트를 전달 (NDEF 메시지가 없을 때도 작동) -->
                <action android:name="android.nfc.action.TAG_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

            <!-- NFC 기술 인텐트 필터 -->
            <intent-filter>
                <!-- NFC 태그에 기술 스택이 감지되면 앱으로 인텐트를 전달 (특정 기술 유형을 지원하는 태그를 처리) -->
                <action android:name="android.nfc.action.TECH_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

            <!-- NFC 기술 필터 설정 (nfc_tech_filter.xml 파일을 참조하여 특정 기술을 처리) -->
            <meta-data
                android:name="android.nfc.action.TECH_DISCOVERED"
                android:resource="@xml/nfc_tech_filter" />
        </activity>
    </application>

</manifest>

 

nfc_tech_filter.xml 사용 예 :

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <!-- Ndef 태그 (NFC Data Exchange Format) -->
    <tech-list>
        <tech>android.nfc.tech.Ndef</tech>
    </tech-list>

    <!-- NdefFormatable 태그 (NFC 태그를 NDEF 형식으로 포맷할 수 있는 경우) -->
    <tech-list>
        <tech>android.nfc.tech.NdefFormatable</tech>
    </tech-list>

    <!-- NfcA 태그 (ISO 14443-3A) -->
    <tech-list>
        <tech>android.nfc.tech.NfcA</tech>
    </tech-list>

    <!-- NfcB 태그 (ISO 14443-3B) -->
    <tech-list>
        <tech>android.nfc.tech.NfcB</tech>
    </tech-list>

    <!-- NfcF 태그 (Felica, JIS 6319-4) -->
    <tech-list>
        <tech>android.nfc.tech.NfcF</tech>
    </tech-list>

    <!-- NfcV 태그 (ISO 15693) -->
    <tech-list>
        <tech>android.nfc.tech.NfcV</tech>
    </tech-list>

    <!-- IsoDep 태그 (ISO 14443-4) -->
    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
    </tech-list>

    <!-- MifareClassic 태그 -->
    <tech-list>
        <tech>android.nfc.tech.MifareClassic</tech>
    </tech-list>

    <!-- MifareUltralight 태그 -->
    <tech-list>
        <tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>



    <!-- 기술 조합이 필요한 경우 -->
    <!-- NfcA와 Ndef, MifareUltralight를 동시에 지원하는 태그 -->
    <tech-list>
        <tech>android.nfc.tech.NfcA</tech>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>
</resources>

 

 

반응형
블로그 이미지

SKY STORY

,
반응형

NdefFormatable은 NFC 태그를 NDEF 형식으로 포맷할 수 있을 때 사용하는 인터페이스이다. 만약 NFC 태그가 NDEF 형식으로 포맷되지 않은 경우, NDEF 포맷팅 작업을 수행할 수 있다. 이 기능은 주로 NFC 태그가 기본적으로 NDEF 메시지를 지원하지 않는 경우에 사용된다. NDEF 포맷팅은 태그에 데이터를 기록할 수 있는 형태로 변환하는 과정을 말한다.

NdefFormatable 사용 시점:

  • NFC 태그가 아직 NDEF 포맷으로 되어 있지 않은 경우 NdefFormatable을 사용하여 태그를 포맷하고 NDEF 메시지를 기록할 수 있다.
  • 한 번만 포맷할 수 있는 태그, 비어 있는 상태에서만 포맷할 수 있는 태그에 유용하다.

일반적으로 NFC 태그를 처음 사용하거나 비어 있는 상태에서 NDEF 메시지를 기록하려는 경우 NdefFormatable은 포맷할 수 있는 태그에 대해 connect(), format()  writeNdefMessage()와 같은 작업을 제공한다.

다음은 NdefFormatable을 사용하여 NFC 태그를 NDEF 형식으로 포맷하고 데이터를 기록하는 예이다.

fun formatTagWithNdefMessage(tag: Tag, ndefMessage: NdefMessage): Boolean {
    val ndefFormatable = NdefFormatable.get(tag)

    if (ndefFormatable != null) {
        try {
            // NDEF 포맷팅을 위해 태그 연결
            ndefFormatable.connect()

            // NDEF 메시지로 태그 포맷 및 데이터 기록
            ndefFormatable.format(ndefMessage)

            Log.d("NFC", "태그를 NDEF 형식으로 포맷하고 메시지를 기록했습니다.")
            return true
        } catch (e: IOException) {
            Log.e("NFC", "태그 포맷팅 실패: ${e.message}")
        } finally {
            try {
                ndefFormatable.close()
            } catch (e: IOException) {
                Log.e("NFC", "태그 닫기 실패: ${e.message}")
            }
        }
    } else {
        Log.e("NFC", "이 태그는 NDEF 포맷이 지원되지 않습니다.")
    }

    return false
}
 

 

주요 메서드 설명:

  • NdefFormatable.get(tag): Tag 객체에서 NdefFormatable 인스턴스를 얻는다. NDEF 포맷 미지원 시 null을 반환 한다.
  • connect(): NFC 태그와 연결을 시도한다.
  • format(NdefMessage): 태그를 NDEF 형식으로 포맷하고, 제공된 NDEF 메시지를 기록한다.
  • close(): 태그와의 연결 종료한다.

주의사항:

  • NDEF 형식으로 포맷하면 원래 상태로 되돌릴 수 없다.
  • 일부 태그는 포맷 후에도 더 이상 기록할 수 없는 경우가 있다. (읽기 전용 태그로 변환).
  • 모든 태그가 NDEF 포맷을 지원하지 않는다.

 

반응형

'개발 > Android' 카테고리의 다른 글

멀티라인 Toast 메시지 출력  (0) 2024.10.24
NFC 권한설정  (1) 2024.10.21
NFC Tag 상세정보 출력  (2) 2024.10.18
안드로이드 각 버전별 추가된 주요 기능  (0) 2024.10.15
JIT, AOT 컴파일 비교  (1) 2024.10.15
블로그 이미지

SKY STORY

,
반응형

NFC Tag에서 얻을 수 있는 정보를 json 문자열 포맷으로 반환하는 함수를 만들어보았다.

테그 정보 :

  1. UID (고유 ID): tag.id에서 추출한 고유 식별자.
  2. Tech List: 태그가 지원하는 모든 기술 목록 (tag.techList).
  3. NfcA (ISO 14443-3A): ATQA, SAK, 최대 전송 길이, 타임아웃 정보.
  4. IsoDep (ISO 14443-4): 역사적 바이트(historicalBytes), 고층 응답(hiLayerResponse), 최대 전송 길이, 타임아웃 정보.
  5. NfcB (ISO 14443-3B): 응용 데이터, 프로토콜 정보, 최대 전송 길이.
  6. NfcF (Felica): 제조사 정보, 시스템 코드, 최대 전송 길이, 타임아웃.
  7. NfcV (ISO 15693): 응답 플래그, DSFID, 최대 전송 길이.
  8. MifareClassic: 타입, 크기, 섹터 블록 .
  9. MifareUltralight: 타입 정보 (Ultralight, Ultralight C).

 

구현 함수는 다음과 같다.

fun readTagDetailsAsJson(tag: Tag): String {
    val tagInfo = mutableMapOf<String, Any>()

    // UID 정보 (태그 고유 ID)
    tagInfo["uid"] = tag.id.joinToString("") { String.format("%02X", it) }

    // 기술 스택 나열 (태그가 지원하는 모든 기술들)
    tagInfo["techList"] = tag.techList.joinToString()

    // NfcA (ISO 14443-3A) 정보
    val nfcA = NfcA.get(tag)
    if (nfcA != null) {
        tagInfo["NfcA"] = mapOf(
            // ATQA: Answer to Request Type A. 태그가 리더기에 응답할 때 사용하는 정보
            "atqa" to nfcA.atqa.joinToString("") { String.format("%02X", it) },
            // SAK: Select Acknowledge. 태그가 리더기에 제공하는 지원 기능 정보
            "sak" to String.format("%02X", nfcA.sak),
            // 한 번에 보낼 수 있는 최대 전송 길이 (바이트 단위)
            "maxTransceiveLength" to nfcA.maxTransceiveLength,
            // 통신 타임아웃 시간 (밀리초 단위)
            "timeout" to nfcA.timeout
        )
    }

    // IsoDep (ISO 14443-4) 정보
    val isoDep = IsoDep.get(tag)
    if (isoDep != null) {
        tagInfo["IsoDep"] = mapOf(
            // Historical Bytes: 태그가 초기 통신 설정 시 리더기에 제공하는 추가 정보
            "historicalBytes" to (isoDep.historicalBytes?.joinToString("") { String.format("%02X", it) } ?: "N/A"),
            // Higher Layer Response: 고급 프로토콜에 대한 응답 정보
            "hiLayerResponse" to (isoDep.hiLayerResponse?.joinToString("") { String.format("%02X", it) } ?: "N/A"),
            // 한 번에 전송할 수 있는 최대 데이터 길이 (바이트 단위)
            "maxTransceiveLength" to (isoDep.maxTransceiveLength.toString() ?: "N/A"),
            // 통신 타임아웃 시간 (밀리초 단위)
            "timeout" to isoDep.timeout
        )
    }

    // NfcB (ISO 14443-3B) 정보
    val nfcB = NfcB.get(tag)
    if (nfcB != null) {
        tagInfo["NfcB"] = mapOf(
            // Application Data: 태그의 애플리케이션과 관련된 데이터
            "applicationData" to (nfcB.applicationData?.joinToString("") { String.format("%02X", it) } ?: "N/A"),
            // Protocol Info: 태그가 제공하는 프로토콜 정보
            "protocolInfo" to (nfcB.protocolInfo?.joinToString("") { String.format("%02X", it) } ?: "N/A")
        )
    }

    // NfcF (Felica, JIS 6319-4) 정보
    val nfcF = NfcF.get(tag)
    if (nfcF != null) {
        tagInfo["NfcF"] = mapOf(
            // 제조사 코드
            "manufacturer" to (nfcF.manufacturer?.joinToString("") { String.format("%02X", it) } ?: "N/A"),
            // 시스템 코드
            "systemCode" to (nfcF.systemCode?.joinToString("") { String.format("%02X", it) } ?: "N/A"),
            // 한 번에 전송할 수 있는 최대 데이터 길이 (바이트 단위)
            "maxTransceiveLength" to nfcF.maxTransceiveLength.toString(),
            // 통신 타임아웃 시간 (밀리초 단위)
            "timeout" to nfcF.timeout.toString()
        )
    }

    // NfcV (ISO 15693) 정보
    val nfcV = NfcV.get(tag)
    if (nfcV != null) {
        tagInfo["NfcV"] = mapOf(
            // 응답 플래그
            "responseFlags" to String.format("%02X", nfcV.responseFlags),
            // DSFID (Data Storage Format Identifier)
            "dsfId" to String.format("%02X", nfcV.dsfId),
            // 한 번에 전송할 수 있는 최대 데이터 길이 (바이트 단위)
            "maxTransceiveLength" to nfcV.maxTransceiveLength
        )
    }

    // NfcBarcode (Type V or JIS 6319-4) 정보 - maxTransceiveLength 지원 없음
    val nfcBarcode = NfcBarcode.get(tag)
    if (nfcBarcode != null) {
        tagInfo["NfcBarcode"] = mapOf(
            // 바코드 타입 정보 (Type V or JIS 6319-4)
            "type" to nfcBarcode.type
        )
    }

    // MifareClassic 정보
    val mifareClassic = MifareClassic.get(tag)
    if (mifareClassic != null) {
        tagInfo["MifareClassic"] = mapOf(
            // MifareClassic 타입 (Classic, Plus, Pro)
            "type" to when (mifareClassic.type) {
                MifareClassic.TYPE_CLASSIC -> "Classic"
                MifareClassic.TYPE_PLUS -> "Plus"
                MifareClassic.TYPE_PRO -> "Pro"
                else -> "Unknown"
            },
            // MifareClassic 메모리 크기
            "size" to mifareClassic.size,
            // 섹터 개수
            "sectorCount" to mifareClassic.sectorCount,
            // 블록 개수
            "blockCount" to mifareClassic.blockCount
        )
    }

    // MifareUltralight 정보
    val mifareUltralight = MifareUltralight.get(tag)
    if (mifareUltralight != null) {
        tagInfo["MifareUltralight"] = mapOf(
            // MifareUltralight 타입 (Ultralight, Ultralight C)
            "type" to when (mifareUltralight.type) {
                MifareUltralight.TYPE_ULTRALIGHT -> "Ultralight"
                MifareUltralight.TYPE_ULTRALIGHT_C -> "Ultralight C"
                else -> "Unknown"
            }
        )
    }

    // JSON 형태로 변환하여 반환
    return JSONObject(tagInfo as Map<*, *>).toString()
}

 

결과값 로그 출력 값은 아래와 같다.

로그 출력 값 예 :
{
  "uid": "641B27A2B67881",  // 시리얼 번호
  "techList": "android.nfc.tech.IsoDep, android.nfc.tech.NfcA", // 태그에서 지원하는 NFC 기술 목록
  "NfcA": {		// NfcA (ISO/IEC 14443-3A) 기술에 대한 정보
    "atqa": "4800",		// Answer to Request, Type A (ATQA) 값
    "sak": "20",		// Select Acknowledge (SAK) 값
    "maxTransceiveLength": 253,	// NFC 통신에서 한 번에 전송할 수 있는 최대 데이터 길이(바이트)
    "timeout": 618		// 통신 타임아웃 시간(밀리초)
  },
  "IsoDep": {	// IsoDep (ISO/IEC 14443-4) 기술에 대한 정보
    "historicalBytes": "",	// NFC 태그가 최초로 통신을 시작할 때 제공하는 부가적인 정보를 포함
    "hiLayerResponse": "N\/A",	// 태그가 고급 프로토콜을 지원하는 경우 제공
    "maxTransceiveLength": "65279",// IsoDep에서 한 번에 전송할 수 있는 최대 데이터 길이
    "timeout": 618		// 통신 타임아웃 시간(밀리초)
  }
}

 

 

반응형

'개발 > Android' 카테고리의 다른 글

NFC 권한설정  (1) 2024.10.21
NdefFormatable 태그  (0) 2024.10.21
안드로이드 각 버전별 추가된 주요 기능  (0) 2024.10.15
JIT, AOT 컴파일 비교  (1) 2024.10.15
FCM 푸시설정  (0) 2024.10.14
블로그 이미지

SKY STORY

,
반응형

앱스토어 배포를 위해 리뷰 전 'Validate App' 하면 아래 그림과 같이 NDEF 에러가 발생하는 경우가 있다. 이 해결방법을 알아보도록 하겠다.

 

1. Info.plist에 NFC관련 권한이 설정되어있는지 확인하자. 

<key>NFCReaderUsageDescription</key>
<string>This app would like to use NFC for some reason.</string>

 

2. 해당  앱의 App ID 설정에 'NFC Tage Reading'이 체크되어있는지 확인한다.

 

3. .entitlements 파일에 TAG설정이 되어있는지 확인하고 NDEF 부분은 제거한다.

<?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  <plist version="1.0">
  <dict>
      <key>com.apple.developer.nfc.readersession.formats</key>
      <array>
          <string>TAG</string>
          <!-- 'NDEF는 제거하도록 한다. (대부분 이부분 때문에 에러 발생됨)
          <string>NDEF</string>
          -->
      </array>
  </dict>
  </plist>

 

4. Clean & ReBuild

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
블로그 이미지

SKY STORY

,
반응형

NFC NDEF tag 및 feliCa, iso7816, iso15693, miFare tag  읽기 / 쓰기 관련 클래스를 만들어 보았다.

//
//  HiNfcManager.swift
//  NFC
//
//  Created by netcanis on 2023/04/18.
//

import UIKit
import CoreNFC

public enum HiNFCError: Error {
    case unavailable
    case notSupported
    case readOnly
    case invalidPayloadSize
    case invalidated(errorDescription: String)
}

open class HiNFCManager: NSObject {
    static let shared = HiNFCManager()
    
    public typealias DidBecomeActive = (HiNFCManager) -> Void
    public typealias DidDetect = (HiNFCManager, Result<[String: Any]?, HiNFCError>) -> Void
    
    private enum HiNFCAction {
        case read
        case write(message: NFCNDEFMessage)
    }
    
    // MARK: - Properties
    private var didBecomeActive: DidBecomeActive?
    private var didDetect: DidDetect?
    private var action: HiNFCAction?
    
    // MARK: - Properties (NDEF, Tag)
    open private(set) var ndefSession: NFCNDEFReaderSession?
    open private(set) var tagSession: Any?
    
    
    
    // MARK: - NFCNDEFTag
    open func read(didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCNDEFReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        let session = NFCNDEFReaderSession(delegate: self,
                                           queue: nil,
                                           invalidateAfterFirstRead: true)
        action = .read
        startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
    }
    
    open func write(message: [String: Any], didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCNDEFReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        if #available(iOS 13.0, *) {
            let session = NFCNDEFReaderSession(delegate: self,
                                               queue: nil,
                                               invalidateAfterFirstRead: false)
            
            let payload = message["payload"] as? String ?? ""
            let type = message["type"] as? String ?? "T"
            let format: NFCTypeNameFormat = (type == "T") ? (.media) : (.absoluteURI)
            
            let payloadData = payload.data(using: .utf8)!
            let typeData = type.data(using: .utf8)!
            let ndefPayload = NFCNDEFPayload(format: format, type: typeData, identifier: Data(), payload: payloadData)
            let ndefMessage = NFCNDEFMessage(records: [ndefPayload])
            
            action = .write(message: ndefMessage)
            startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
        }
    }
    
    open func setMessage(_ alertMessage: String) {
        ndefSession?.alertMessage = alertMessage
    }
    
    
    
    // MARK: - NFCTag
    open func readTag(didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        if #available(iOS 13.0, *) {
            let session = NFCTagReaderSession(pollingOption: [.iso14443, .iso15693, .iso18092],
                                              delegate: self,
                                              queue: nil)!
            action = .read
            startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
        }
    }
    
    open func writeTag(message: [String: Any], didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        if #available(iOS 13.0, *) {
            let session = NFCTagReaderSession(pollingOption: [.iso14443, .iso15693, .iso18092],
                                              delegate: self,
                                              queue: nil)!
            
            let payload = message["payload"] as? String ?? ""
            let type = message["type"] as? String ?? "T"
            let format: NFCTypeNameFormat = (type == "T") ? (.media) : (.absoluteURI)
            
            let payloadData = payload.data(using: .utf8)!
            let typeData = type.data(using: .utf8)!
            let ndefPayload = NFCNDEFPayload(format: format, type: typeData, identifier: Data(), payload: payloadData)
            let ndefMessage = NFCNDEFMessage(records: [ndefPayload])

            action = .write(message: ndefMessage)
            startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
        }
    }
    
    open func setTagMessage(_ alertMessage: String) {
        if #available(iOS 13.0, *) {
            if let session = self.tagSession as? NFCTagReaderSession {
                session.alertMessage = alertMessage
            }
        }
    }
}



// MARK: - NFCNDEFReaderSessionDelegate
extension HiNFCManager : NFCNDEFReaderSessionDelegate {
    
    open func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
        self.didBecomeActive?(self)
    }
    
    open func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
        // iOS 13미만
        guard let message = messages.first, let record = message.records.first else {
            self.didDetect?(self, .failure(.invalidated(errorDescription: "NDEF message나 message record가 없습니다.")))
            self.invalidate(errorMessage: "NDEF message나 message record가 없습니다.")
            return
        }
        
        
        let language = String(data: record.payload.advanced(by: 1), encoding: .utf8)
        let encoding = record.payload[0] & NFCTypeNameFormat.nfcWellKnown.rawValue
        let textData = record.payload.advanced(by: 3)
        let text = String(data: textData, encoding: .utf8)
        
        let result: [String: Any] = [
            "Type":record.type.string,
            "Format": self.formattedTNF(from: record.typeNameFormat),
            "Value": [
                "Encoding": "\(encoding)",
                "Language": "\(language ?? "")",
                "Text": "\(text ?? "")"
            ],
            "Raw value": record.payload.string,
            "Payload": "\(record.payload.hexStringFormatted)",
            "Size": "\(record.payload.count)",
        ]
        print("\(String(describing: result.toJsonString()))")
        
        self.didDetect?(self, .success(result))
        self.invalidate(errorMessage: "NDEF message read successfully.")
    }
    
    @available(iOS 13.0, *)
    open func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
        guard tags.count == 1, let tag = tags.first else {
            DispatchQueue.global().asyncAfter(deadline: .now() + .microseconds(500)) {
                session.restartPolling()
            }
            return
        }
        
        session.connect(to: tag) { [weak self] error in
            guard let self = self else { return }
            if error != nil {
                self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                self.invalidate(errorMessage: error?.localizedDescription)
                return
            }
            
            tag.queryNDEFStatus { status, capacity, error in
                switch (status, self.action) {
                case (.notSupported, _):
                    self.didDetect?(self, .failure(.notSupported))
                    self.invalidate(errorMessage: error?.localizedDescription)

                case (.readOnly, _):
                    self.didDetect?(self, .failure(.readOnly))

                case (.readWrite, .read):
                    tag.readNDEF { message, error in
                        if error != nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error?.localizedDescription)
                            return
                        }
                        
                        let record = message?.records.first
                        let result: [String: Any] = [
                            "Type":record?.type.string ?? "",
                            "Format": self.formattedTNF(from: record!.typeNameFormat),
                            "Raw value": record?.payload.string ?? "",
                            "Payload": "\(record?.payload.hexStringFormatted ?? "")",
                            "Size": "\(record?.payload.count ?? 0)",
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }

                case (.readWrite, .write(let message)):
                    guard message.length <= capacity else {
                        self.didDetect?(self, .failure(.invalidPayloadSize))
                        self.invalidate(errorMessage: "Invalid payload size")
                        return
                    }

                    tag.writeNDEF(message) { error in
                        if error != nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error!.localizedDescription)
                            return
                        }
                        let result: [String: Any] = [
                            "result":message.records.first?.payload.string ?? ""
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }
                default:
                    return
                }
            }
        }
    }
    
    open func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
        if let error = error as? NFCReaderError,
           error.code != .readerSessionInvalidationErrorFirstNDEFTagRead &&
            error.code != .readerSessionInvalidationErrorUserCanceled {
            self.didDetect?(self, .failure(.invalidated(errorDescription: error.localizedDescription)))
        }
        self.ndefSession = nil
        self.tagSession = nil
        self.didBecomeActive = nil
        self.didDetect = nil
    }
}


// MARK: - Private helper functions
extension HiNFCManager {
    private func invalidate(errorMessage: String?) {
        if errorMessage != nil {
            if #available(iOS 13.0, *) {
                if let ns = ndefSession {
                    ns.invalidate(errorMessage: errorMessage!)
                }
                if let ts = tagSession as? NFCTagReaderSession {
                    ts.invalidate(errorMessage: errorMessage!)
                }
            } else {
                if let ns = ndefSession {
                    ns.invalidate()
                }
                if #available(iOS 13.0, *) {
                    if let ts = tagSession as? NFCTagReaderSession {
                        ts.invalidate()
                    }
                }
            }
        } else {
            if let ns = ndefSession {
                ns.invalidate()
            }
            if #available(iOS 13.0, *) {
                if let ts = tagSession as? NFCTagReaderSession {
                    ts.invalidate()
                }
            }
        }
        ndefSession = nil
        tagSession = nil
        didBecomeActive = nil
        didDetect = nil
    }
    
    private func startSession(session: NFCNDEFReaderSession,
                              didBecomeActive: DidBecomeActive?,
                              didDetect: @escaping DidDetect) {
        self.ndefSession = session
        self.didBecomeActive = didBecomeActive
        self.didDetect = didDetect
        session.begin()
    }
    
    @available(iOS 13.0, *)
    private func startSession(session: NFCTagReaderSession,
                              didBecomeActive: DidBecomeActive?,
                              didDetect: @escaping DidDetect) {
        self.tagSession = session
        self.didBecomeActive = didBecomeActive
        self.didDetect = didDetect
        session.begin()
    }
    
    private func formattedTNF(from tnf: NFCTypeNameFormat) -> String {
        switch tnf {
        case .empty:        // 0: 이름 없는 레코드 (빈 문자열로 표시됨)
            return "Empty (0x00)"
        case .nfcWellKnown: // 1: NFC Forum에서 정의한 레코드 형식
            return "NFC Well Known (0x01)"
        case .media:        // 2: 미디어 타입의 레코드 (ex. 'audio/mp3')
            return "Media (0x02)"
        case .absoluteURI:  // 3: URI 형식의 레코드
            return "Absolute URI (0x03)"
        case .nfcExternal:  // 4: NFC 포럼에서 정의한 외부 레코드
            return "NFC External (0x04)"
        case .unchanged:    // 6: 이름 형식 변경 없음. (현재의 이름 형식을 유지)
            return "Unchanged (0x06)"
        default:            // 5: 알 수 없는 레코드
            return "Unknown (0x05)"
        }
    }
}





// MARK: - NFCTagReaderSessionDelegate
@available(iOS 13.0, *)
extension HiNFCManager : NFCTagReaderSessionDelegate {
    
    public func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
        self.didBecomeActive?(self)
    }
    
    public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        guard tags.count == 1, let tag = tags.first else {
            DispatchQueue.global().asyncAfter(deadline: .now() + .microseconds(500)) {
                session.restartPolling()
            }
            return
        }
        
        var ndefTag: NFCNDEFTag
        switch tag {
        case let .feliCa(tag):  /// FeliCa tag. (NFCFeliCaTag)
            ndefTag = tag
            self.parseFeliCaTag(tag)
        case let .iso7816(tag): /// ISO14443-4 type A / B tag with ISO7816 communication. (NFCISO7816Tag)
            ndefTag = tag
            self.parseISO7816Tag(tag)
        case let .iso15693(tag):/// ISO15693 tag.
            ndefTag = tag
            self.parseISO15693Tag(tag)
        case let .miFare(tag):  /// MiFare technology tag (MIFARE Plus, UltraLight, DESFire) base on ISO14443. (NFCMiFareTag)
            ndefTag = tag
            self.parseMIFARETag(tag)
        @unknown default:
            self.didDetect?(self, .failure(.invalidated(errorDescription: "Tag not valid.")))
            self.invalidate(errorMessage: "Tag not valid.")
            return
        }
        
        
        session.connect(to: tag) { [weak self] error in
            guard let self = self else { return }
            if error != nil {
                self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                self.invalidate(errorMessage: error?.localizedDescription)
                return
            }
            
            ndefTag.queryNDEFStatus { status, capacity, error in
                switch (status, self.action) {
                case (.notSupported, _):
                    self.didDetect?(self, .failure(.notSupported))
                    self.invalidate(errorMessage: error?.localizedDescription)

                case (.readOnly, _):
                    self.didDetect?(self, .failure(.readOnly))

                case (.readWrite, .read):
                    ndefTag.readNDEF { (message, error) in
                        if error != nil || message == nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error?.localizedDescription)
                            return
                        }

                        let record = message?.records.first
                        let result: [String: Any] = [
                            "Type":record?.type.string ?? "",
                            "Format": self.formattedTNF(from: record!.typeNameFormat),
                            "Raw value": record?.payload.string ?? "",
                            "Payload": "\(record?.payload.hexStringFormatted ?? "")",
                            "Size": "\(record?.payload.count ?? 0)",
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }
                case (.readWrite, .write(let message)):
                    guard message.length <= capacity else {
                        self.didDetect?(self, .failure(.invalidPayloadSize))
                        self.invalidate(errorMessage: "Invalid payload size")
                        return
                    }

                    ndefTag.writeNDEF(message) { error in
                        if error != nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error!.localizedDescription)
                            return
                        }
                        let result: [String: Any] = [
                            "result":message.records.first?.payload.string ?? ""
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }
                default:
                    return
                }
            }
        }
    }
    
    public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
        // NFC 태그 읽기가 중단되었을 때, 호출된다.
        print("NFCTag 읽기 중단: \(error.localizedDescription)")
    }
    
}


// MARK: - Private helper functions (for NFCTag)
@available(iOS 13.0, *)
extension HiNFCManager {
    /// FeliCa tag. (NFCFeliCaTag)
    private func parseFeliCaTag(_ tag: NFCFeliCaTag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ FeliCa tag ] ::::::::::
                    "- Type : FeliCa tag."
                    "- currentIDm : \(String(describing: tag.currentIDm))"
                    "- currentSystemCode : \(String(describing: tag.currentSystemCode))"
                    "- description : \(String(describing: tag.description))"
                    ------------------------------------
                    """
        print(log)
    }
    
    /// ISO14443-4 type A / B tag with ISO7816 communication. (NFCISO7816Tag)
    private func parseISO7816Tag(_ tag: NFCISO7816Tag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ ISO7816 tag ] ::::::::::
                    "- Type : ISO14443-4 type A / B tag with ISO7816 communication."
                    "- Identifier : \(String(describing: tag.identifier))"
                    "- historicalBytes : \(String(describing: tag.historicalBytes?.hexStringFormatted))"
                    "- applicationData : \(String(describing: tag.applicationData?.hexStringFormatted))"
                    "- initialSelectedAID : \(tag.initialSelectedAID)"
                    "- proprietaryApplicationDataCoding : \(tag.proprietaryApplicationDataCoding)"
                    "- description : \(String(describing: tag.description))"
                    ------------------------------------
                    """
        print(log)
    }

    /// ISO15693 tag.
    private func parseISO15693Tag(_ tag: NFCISO15693Tag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ ISO15693 tag ] ::::::::::
                    "- Type : ISO15693 tag."
                    "- Identifier : \(tag.identifier.hexString)"
                    "- icSerialNumber : \(String(describing: tag.icSerialNumber.hexStringFormatted))" // IC 시리얼 번호(IC serial number)
                    "- icManufacturerCode : \(String(describing: tag.icManufacturerCode))" // C 제조사 코드(IC manufacturer code)
                    "- description : \(String(describing: tag.description))"
                    ------------------------------------
                    """
        print(log)
    }
    
    /// MiFare technology tag (MIFARE Plus, UltraLight, DESFire) base on ISO14443. (NFCMiFareTag)
    private func parseMIFARETag(_ tag: NFCMiFareTag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ MiFare tag ] ::::::::::
                    "- Type : MiFare technology tag (MIFARE Plus, UltraLight, DESFire) base on ISO14443."
                    "- Identifier : \(tag.identifier.hexString)"
                    "- Historical bytes : \(tag.historicalBytes?.hexString ?? "None")"
                    "- mifareFamily : \(self.formattedMiFareFamily(tag))"
                    "- description : \(String(describing: tag.description))"
                    "- Block count : \(tag.mifareFamily == .plus ? 256 : 16)"
                    ------------------------------------
                    """
        print(log)
    }
    
    private func formattedMiFareFamily (_ tag: NFCMiFareTag) -> String {
        switch (tag.mifareFamily) {
        case .unknown:
            return "MiFare compatible ISO14443 Type A tag." // ISO14443 Type A 호환제품
        case .ultralight:
            return "MiFare Ultralight series."
        case .plus:
            return "MiFare Plus series."
        case .desfire:
            return "MiFare DESFire series."
        default:
            return "Unknown"
        }
    }
}



extension Data {
    // Data([0x12, 0x34, 0x56]) -> 123456
    var hexString: String {
        return map { String(format: "%02hhx", $0) }.joined() // big-endian byte order : reversed().map
    }
    
    // Data([0x12, 0x34, 0x56]) -> 0x12 0x34 0x56
    var hexStringFormatted: String {
        let hexArray = map { String(format: "0x%02hhx", $0) }
        return hexArray.joined(separator: " ")
    }
    
    // Data([0x12, 0x34, 0x56]) -> 313233343536
    public func hexEncodedString() -> String {
        let hexDigits = Array("0123456789abcdef".utf16)
        var hexChars = [UTF16.CodeUnit]()
        hexChars.reserveCapacity(count * 2)
        for byte in self {
            let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
            hexChars.append(hexDigits[index1])
            hexChars.append(hexDigits[index2])
        }
        return String(utf16CodeUnits: hexChars, count: hexChars.count)
    }
    
    var string: String {
        return String(data: self, encoding: .utf8)!
    }
    
    var jsonString: String {
        do {
            let json = try JSONSerialization.jsonObject(with: self, options: [])
            let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
            return String(data: data, encoding: .utf8) ?? ""
        } catch let error {
            print("JSON serialization error: \(error.localizedDescription)")
            return ""
        }
    }
    
    func isJsonString() -> Bool {
        do {
            let _ = try JSONSerialization.jsonObject(with: self, options: [])
            return true
        } catch {
            return false
        }
    }
}

extension Dictionary {
    func toJsonString() -> String? {
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: self, options: [.prettyPrinted, .sortedKeys])
            return String(data: jsonData, encoding: .utf8)
        } catch {
            print(error.localizedDescription)
            return nil
        }
    }
}

extension Array where Element == Data {
    func toStrings() -> [String] {
        return self.map { String(data: $0, encoding: .utf8) ?? "" }
    }
    
    func toHexStrings() -> [String] {
        return self.map { $0.reduce("") { $0 + String(format: "%02x", $1) } }
    }
}

extension NSObject {
    func rootWindow() -> UIWindow? {
        var window: UIWindow?
        if #available(iOS 15.0, *) {
            window = UIApplication.shared.connectedScenes
                .compactMap { $0 as? UIWindowScene }
                .flatMap { $0.windows }
                .first(where: { $0.isKeyWindow })
        } else {
            window = UIApplication.shared.windows.first(where: { $0.isKeyWindow })
        }
        return window
    }
    
    func showAlert(title: String?, message: String?, actions: [UIAlertAction] = [UIAlertAction(title: "OK", style: .default, handler: nil)], preferredStyle: UIAlertController.Style = .alert) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: preferredStyle)
        for action in actions {
            alertController.addAction(action)
        }
        guard let rootViewController = self.rootWindow()?.rootViewController else { return }
        rootViewController.present(alertController, animated: true)
    }
}

 

사용법 :

// NFC NDEF 테그 쓰기
let str = "테스트 메시지 입니다."
let type = str.hasPrefix("http") == true ? "U" : "T"

let message: [String: Any] = [
    "payload": str,
    "type": type
]
HiNFCManager.shared.write(message: message) { manager in
    manager.setMessage("Place iPhone near the tag to be written on")
} didDetect: { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to write tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag successfully written")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}


// NFC NDEF 테그 읽기
HiNFCManager.shared.read { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to read tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag read successfully")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}




// NFC Tag 쓰기
let str = "https://www.apple.com"
let type = str.hasPrefix("http") == true ? "U" : "T"

let message: [String: Any] = [
    "payload": str,
    "type": type
]
HiNFCManager.shared.writeTag(message: message) { manager in
    manager.setMessage("Place iPhone near the tag to be written on")
} didDetect: { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to write tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag successfully written")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}


// NFC Tag 읽기
HiNFCManager.shared.readTag { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to read tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag read successfully")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}

 

결과값 로그 :

// NFC NDEF tag 읽기결과 로그
{
  "Format" : "Media (0x02)",
  "Payload" : "0x74 0x65 0x73 0x74 0x20 0x6d 0x65 0x73 0x73 0x61 0x67 0x65",
  "Raw value" : "test message",
  "Size" : "12",
  "Type" : "T"
}

// NFC NDEF tag 쓰기결과 로그
{
  "result" : "test message"
}


// NFC Tag 읽기결과 로그
{
  "Format" : "Absolute URI (0x03)",
  "Payload" : "0x68 0x74 0x74 0x70 0x73 ... 0x65 0x2e 0x63 0x6f 0x6d",
  "Raw value" : "https://www.apple.com",
  "Size" : "21",
  "Type" : "U"
}

// NFC Tag 쓰기결과 로그
{
  "result" : "https:\/\/www.apple.com"
}

 

 

2023.04.26 - [iOS] - NFC tag read/write Manager Class (1/2)

2023.04.26 - [iOS] - NFC tag read/write Manager Class (2/2)

 

반응형

'개발 > iOS' 카테고리의 다른 글

XOR 연산을 이용한 문자열 비교  (0) 2023.12.07
Rosetta2  (0) 2023.04.26
NFC tag read/write Manager Class (1/2)  (0) 2023.04.26
Carthage 설치 및 제거  (0) 2023.01.11
NSURLSessionTask 캐싱 비활성화  (0) 2022.10.24
블로그 이미지

SKY STORY

,
반응형

NFC NDEF tag 읽기 / 쓰기 및 feliCa, iso7816, iso15693, miFare tag 읽기 관련 클래스를

만들기 위해 우선 다음과 같이 환경 설정부터 진행해 보자.

 

우선 개발자 사이트에서 App ID 설정에서 NFC 읽기 활성화 한다.

Xcode에서는 다음과 같이 적용된다.

entitlements 파일에 NDEF, TAG 등록.

Info.plist 권한 설정은 다음과 같다.

NFC 읽기 권한  설정을 한다.

<key>NFCReaderUsageDescription</key>
<string>결제를 위해 NFC 사용이 승인되어야 합니다.</string>

 

소니의 Felica tag를 읽기 위해 시스템 코드를 등록한다.

<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
<array>
	<string>88B4</string>
	<string>88A8</string>
	<string>8031</string>
	<string>88CA</string>
	<string>8FC7</string>
	<string>8E5E</string>
	<string>8E6F</string>
	<string>8F5B</string>
	<string>869F</string>
	<string>12FC</string>
	<string>0003</string>
	<string>FE00</string>
</array>
</plist>

 

iso7816 tag의 경우 다음과 같이 읽을 id를 등록한다.

<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
	<string>D2760000850101h</string>
	<string>315041592E5359532E4444463031</string>
	<string>D4100000030001</string>
	<string>325041592E5359532E4444463031</string>
	<string>44464D46412E44466172653234313031</string>
	<string>A00000000101</string>
	<string>A000000003000000</string>
	<string>A00000000300037561</string>
	<string>A00000000305076010</string>
	<string>A0000000031010</string>
	<string>A000000003101001</string>
	<string>A000000003101002</string>
	<string>A0000000032010</string>
	<string>A0000000032020</string>
	<string>A0000000033010</string>
	<string>A0000000034010</string>
	<string>A0000000035010</string>
	<string>A000000003534441</string>
	<string>A0000000035350</string>
	<string>A000000003535041</string>
	<string>A0000000036010</string>
	<string>A0000000036020</string>
	<string>A0000000038002</string>
	<string>A0000000038010</string>
	<string>A0000000039010</string>
	<string>A000000003999910</string>
	<string>A0000000040000</string>
	<string>A00000000401</string>
	<string>A0000000041010</string>
	<string>A00000000410101213</string>
	<string>A00000000410101215</string>
	<string>A0000000041010BB5449435301</string>
	<string>A0000000042010</string>
	<string>A0000000042203</string>
	<string>A0000000043010</string>
	<string>A0000000043060</string>
	<string>A000000004306001</string>
	<string>A0000000044010</string>
	<string>A0000000045010</string>
	<string>A0000000045555</string>
	<string>A0000000046000</string>
	<string>A0000000048002</string>
	<string>A0000000049999</string>
	<string>A0000000050001</string>
	<string>A0000000050002</string>
	<string>A0000000090001FF44FF1289</string>
	<string>A0000000101030</string>
	<string>A00000001800</string>
	<string>A0000000181001</string>
	<string>A000000018434D</string>
	<string>A000000018434D00</string>
	<string>A00000002401</string>
	<string>A000000025</string>
	<string>A0000000250000</string>
	<string>A00000002501</string>
	<string>A000000025010104</string>
	<string>A000000025010402</string>
	<string>A000000025010701</string>
	<string>A000000025010801</string>
	<string>A0000000291010</string>
	<string>A00000002945087510100000</string>
	<string>A00000002949034010100001</string>
	<string>A00000002949282010100000</string>
	<string>A000000029564182</string>
	<string>A00000003029057000AD13100101FF</string>
	<string>A0000000308000000000280101</string>
	<string>A0000000421010</string>
	<string>A0000000422010</string>
	<string>A0000000423010</string>
	<string>A0000000424010</string>
	<string>A0000000425010</string>
	<string>A0000000426010</string>
	<string>A00000005945430100</string>
	<string>A000000063504B43532D3135</string>
	<string>A0000000635741502D57494D</string>
	<string>A00000006510</string>
	<string>A0000000651010</string>
	<string>A00000006900</string>
	<string>A000000077010000021000000000003B</string>
	<string>A0000000790100</string>
	<string>A0000000790101</string>
	<string>A0000000790102</string>
	<string>A00000007901F0</string>
	<string>A00000007901F1</string>
	<string>A00000007901F2</string>
	<string>A0000000790200</string>
	<string>A0000000790201</string>
	<string>A00000007902FB</string>
	<string>A00000007902FD</string>
	<string>A00000007902FE</string>
	<string>A0000000790300</string>
	<string>A0000000791201</string>
	<string>A0000000791202</string>
	<string>A0000000871002FF49FF0589</string>
	<string>A00000008810200105C100</string>
	<string>A000000088102201034221</string>
	<string>A000000088102201034321</string>
	<string>A0000000960200</string>
	<string>A000000098</string>
	<string>A0000000980840</string>
	<string>A0000000980848</string>
	<string>A0000001110101</string>
	<string>A0000001110201</string>
	<string>A0000001160300</string>
	<string>A0000001166010</string>
	<string>A0000001166030</string>
	<string>A0000001169000</string>
	<string>A000000116A001</string>
	<string>A000000116DB00</string>
	<string>A000000118010000</string>
	<string>A000000118020000</string>
	<string>A000000118030000</string>
	<string>A000000118040000</string>
	<string>A0000001184543</string>
	<string>A000000118454E</string>
	<string>A0000001211010</string>
	<string>A0000001320001</string>
	<string>A0000001408001</string>
	<string>A0000001410001</string>
	<string>A0000001510000</string>
	<string>A00000015153504341534400</string>
	<string>A0000001523010</string>
	<string>A0000001524010</string>
	<string>A0000001544442</string>
	<string>A0000001570010</string>
	<string>A0000001570020</string>
	<string>A0000001570021</string>
	<string>A0000001570022</string>
	<string>A0000001570023</string>
	<string>A0000001570030</string>
	<string>A0000001570031</string>
	<string>A0000001570040</string>
	<string>A0000001570050</string>
	<string>A0000001570051</string>
	<string>A0000001570100</string>
	<string>A0000001570104</string>
	<string>A0000001570109</string>
	<string>A000000157010A</string>
	<string>A000000157010B</string>
	<string>A000000157010C</string>
	<string>A000000157010D</string>
	<string>A0000001574443</string>
	<string>A0000001574444</string>
	<string>A000000167413000FF</string>
	<string>A000000167413001</string>
	<string>A000000172950001</string>
	<string>A000000177504B43532D3135</string>
	<string>A0000001850002</string>
	<string>A0000001884443</string>
	<string>A0000002040000</string>
	<string>A0000002281010</string>
	<string>A0000002282010</string>
	<string>A00000022820101010</string>
	<string>A0000002471001</string>
	<string>A0000002472001</string>
	<string>A0000002771010</string>
	<string>A00000030600000000000000</string>
	<string>A000000308000010000100</string>
	<string>A00000031510100528</string>
	<string>A0000003156020</string>
	<string>A00000032301</string>
	<string>A0000003230101</string>
	<string>A0000003241010</string>
	<string>A000000333010101</string>
	<string>A000000333010102</string>
	<string>A000000333010103</string>
	<string>A000000333010106</string>
	<string>A000000333010108</string>
	<string>A000000337301000</string>
	<string>A000000337101000</string>
	<string>A000000337102000</string>
	<string>A000000337101001</string>
	<string>A000000337102001</string>
	<string>A000000337601001</string>
	<string>A0000003591010</string>
	<string>A0000003591010028001</string>
	<string>A00000035910100380</string>
	<string>A0000003660001</string>
	<string>A0000003660002</string>
	<string>A0000003710001</string>
	<string>A00000038410</string>
	<string>A00000038420</string>
	<string>A0000003964D66344D0002</string>
	<string>A00000039742544659</string>
	<string>A0000003974349445F0100</string>
	<string>A0000004271010</string>
	<string>A0000004320001</string>
	<string>A0000004360100</string>
	<string>A0000004391010</string>
	<string>A0000004540010</string>
	<string>A0000004540011</string>
	<string>A0000004762010</string>
	<string>A0000004763030</string>
	<string>A0000004766C</string>
	<string>A000000476A010</string>
	<string>A000000476A110</string>
	<string>A000000485</string>
	<string>A0000005241010</string>
	<string>A0000005271002</string>
	<string>A000000527200101</string>
	<string>A000000527210101</string>
	<string>A0000005591010FFFFFFFF8900000100</string>
	<string>A0000005591010FFFFFFFF8900000200</string>
	<string>A0000005591010FFFFFFFF8900000D00</string>
	<string>A0000005591010FFFFFFFF8900000E00</string>
	<string>A0000005591010FFFFFFFF8900000F00</string>
	<string>A0000005591010FFFFFFFF8900001000</string>
	<string>A00000061700</string>
	<string>A0000006200620</string>
	<string>A0000006260101010001</string>
	<string>A0000006351010</string>
	<string>A0000006581010</string>
	<string>A0000006581011</string>
	<string>A0000006582010</string>
	<string>A0000006723010</string>
	<string>A0000006723020</string>
	<string>A0000007705850</string>
	<string>A0000007790000</string>
	<string>B012345678</string>
	<string>D040000001000002</string>
	<string>D040000002000002</string>
	<string>D040000003000002</string>
	<string>D040000004000002</string>
	<string>D04000000B000002</string>
	<string>D04000000C000002</string>
	<string>D04000000D000002</string>
	<string>D040000013000001</string>
	<string>D040000013000001</string>
	<string>D040000013000002</string>
	<string>D040000013000002</string>
	<string>D040000014000001</string>
	<string>D040000015000001</string>
	<string>D040000015000001</string>
	<string>D0400000190001</string>
	<string>D0400000190002</string>
	<string>D0400000190003</string>
	<string>D0400000190004</string>
	<string>D0400000190010</string>
	<string>D268000001</string>
	<string>D276000005</string>
	<string>D276000005AA040360010410</string>
	<string>D276000005AA0503E00401</string>
	<string>D276000005AA0503E00501</string>
	<string>D276000005AA0503E0050101</string>
	<string>D276000005AB0503E0040101</string>
	<string>D27600002200000001</string>
	<string>D27600002200000002</string>
	<string>D27600002200000060</string>
	<string>D276000025</string>
	<string>D27600002545410100</string>
	<string>D27600002545500100</string>
	<string>D27600002547410100</string>
	<string>D276000060</string>
	<string>D2760000850100</string>
	<string>D2760000850101</string>
	<string>D276000118</string>
	<string>D2760001180101</string>
	<string>D27600012401</string>
	<string>D276000124010101FFFF000000010000</string>
	<string>D2760001240102000000000000010000</string>
	<string>D27600012402</string>
	<string>D2760001240200010000000000000000</string>
	<string>D4100000011010</string>
	<string>D5280050218002</string>
	<string>D5780000021010</string>
	<string>D7560000010101</string>
	<string>D7560000300101</string>
	<string>D8040000013010</string>
	<string>E80704007F00070302</string>
	<string>E82881C11702</string>
	<string>E828BD080F</string>
	<string>F0000000030001</string>
</array>
</plist>

 

 

2023.04.26 - [개발노트] - NFC tag read/write Manager Class (1/2)

2023.04.26 - [분류 전체보기] - NFC tag read/write Manager Class (2/2)

 

반응형

'개발 > iOS' 카테고리의 다른 글

Rosetta2  (0) 2023.04.26
NFC tag read/write Manager Class (2/2)  (0) 2023.04.26
Carthage 설치 및 제거  (0) 2023.01.11
NSURLSessionTask 캐싱 비활성화  (0) 2022.10.24
UIScrollView 스크린샷 만들기  (0) 2022.10.24
블로그 이미지

SKY STORY

,
반응형

RFID(무선 주파수 통신) 기술의 총체로 

카드 모드, 태그 쓰기/읽기 모드, P2P 모드로 사용할 수 있다.

 

NDEF (NFC Data Exchange Format)

NFC 데이터 교환에 이용되는 데이터 교환 포멧으로 NFC 포럼에서 정의

 

NDEF Message

기본적인 NFC 데이터 교환 메시지 단위 하나를 NDEF Message라고 한다.

하나의 NDEF Message는 한개 이상의 NDEF Record로 구성되며, 하나의 NDEF Record는 하나의 데이터를 가지고 이를 

'페이로드(payload)' 라고 한다.

 

 

개념도

이미지 : http://ibadrinath.blogspot.kr/2012/07/nfc-data-exchange-format-ndef.html

 

 

NDEF Record 구성

항목

길이

설명

레코드 헤더 (header)

1byte

레코드에 대한 기본적인 정보

타입 길이 (type length)

1byte

데이터 타입의 길이

페이로드 길이 (payload length)

1byte 혹은 4byte

페이로드의 길이

ID 길이 (id length)

1byte

ID 길이

타입 (type)

'타입 길이' byte

레코드가 담고 있는 페이로드의 타입

ID

'ID 길이' byte

페이로드 ID

페이로드 (payload)

'페이로드 길이' byte

레코드가 담고 있는 페이로드

-표 출처 : http://blog.startnfc.com/entry/NDEF

 

 

레코드 헤더(header)의 구성

헤더 항목

길이

설명

MB (Message Begin)

1bit

NDEF 메시지의 레코드는 비트가 1 입니다.

ME (Message End)

1bit

NDEF 메시지의 마지막 레코드는 비트가 1입니다.

CF (Chunk Flag)

1bit

하나의 페이로드를 여러 개의 레코드로 나누어 전송하는 경우가 있는데, 이때 사용합니다.

SR (Short Record)

1bit

비트가 1이면 '페이로드 길이' 크기는 1byte이고, 0이면 4byte입니다.

IL (ID Length)

1bit

레코드 ID 존재하는 경우 비트가 1입니다.

TNF (Type Name Format)

3bit

(별도로 설명)

-표 출처 : http://blog.startnfc.com/entry/NDEF

 

 

NDEF Record - 타입(type)과 TNF

인터넷에 존재하는 대부분의 콘텐츠는 MIME 타입으로 구분이 가능하다.

하지만 NFC에서는 MIME 타입 이외에 다른 타입도 포함한다.

TNF는 위와 같은 타입이 어떤 형태로 되어 있는지를 나타낸다.

Android 에서 사용되는 TNF의 종류는 아래와 같다.

 

TNF_ABSOLUTE_URI

TNF_EMPTY

TNF_EXTERNAL_TYPE

TNF_MIME_MEDIA

TNF_UNCHANGED

TNF_UNKNOWN

TNF_WELL_KNOWN

(자세한 사항은 클릭)

 

 

3비트 TNF의 구성

TNF (Type Name Format)

설명

Empty

0x00

비어있는 레코드. , 페이로드가 없음.

WKT (NFC Forum well-known type)

0x01

NFC 포럼에서 정의한 타입 형식. (: URI, Text, Smart Poster)

MIME (MIME Media type)

0x02

MIME 타입 형식. (: plain/text, image/jpeg)

AURI (Absolute URI type)

0x03

예를 들어 XML 경우 URI 형식의 DTD XML Schema 타입으로 사용함. (http://www.w3.org/TR/html4/strict.dtd, http://www.w3.org/2000/svg)

EXT (NFC Forum external type)

0x04

NFC 포럼에서 정의한 규칙대로, 임의의 타입 형식을 만들어 사용할 있음. (: startnfc.com:U)

Unknown

0x05

없는 형식의 페이로드. 그냥 byte 덩어리로 취급됨.

Unchanged

0x06

데이터를 여러 조각으로 나누어 전송하는 경우 (chunked record) 이전 레코드의 타입과 같은 타입이라는 것을 나타내기 위해 사용.

Reserved

0x07

사용하지 않음.

- 출처http://blog.startnfc.com/entry/NDEF

 

2020/12/16 - [개발노트] - Code 128 Barcode의 Check Digit 계산방법

2020/12/15 - [iOS/Tips] - 디버깅 차단 처리 (Anti Debug)

2020/12/14 - [iOS/Tips] - bundle id 알아내기

2020/12/12 - [AI/Algorithm] - 2D 충돌처리

2020/12/11 - [iOS/Swift] - UIViewController 스위칭

2020/12/11 - [개발노트] - PlantUML 설치 (Mac OS X)

2020/12/11 - [개발노트] - 특수문자 발음

2020/12/10 - [iOS/Objective-C] - 웹뷰에서 javascript 함수 동기식 호출

2020/12/10 - [iOS/Tips] - Fat Static Library 빌드 (2/2)

2020/12/10 - [iOS/Tips] - Fat Static Library 빌드 (1/2)

2020/12/10 - [iOS/Tips] - Custom UserAgent 설정

2020/12/10 - [iOS/Tips] - CocoaPods 설치 및 제거

2020/12/10 - [iOS/Tips] - Clang diagnostic 경고 무시하기

2020/12/10 - [개발노트] - Bluetooth UUID

2020/12/08 - [개발노트] - 모바일 앱 메모리덤프 이슈 해결방법

 

반응형

'개발 > Note' 카테고리의 다른 글

Luhn 알고리즘  (0) 2020.06.02
NFC Xcode 설정  (0) 2020.05.29
NFC Tag 저장용량  (0) 2020.05.29
NDEF  (0) 2020.05.29
Mifare  (0) 2020.05.29
블로그 이미지

SKY STORY

,

NFC Tag 저장용량

개발/Note 2020. 5. 29. 10:06
반응형
반응형

'개발 > Note' 카테고리의 다른 글

NFC Xcode 설정  (0) 2020.05.29
NFC (Near Field Communication)  (0) 2020.05.29
NDEF  (0) 2020.05.29
Mifare  (0) 2020.05.29
QR 코드 결제 타입  (0) 2020.05.29
블로그 이미지

SKY STORY

,