1# NFC标签读写开发指南 2 3<!--Kit: Connectivity Kit--> 4<!--Subsystem: Communication--> 5<!--Owner: @amunra03--> 6<!--Designer: @wenxiaolin--> 7<!--Tester: @zs_111--> 8<!--Adviser: @zhang_yixin13--> 9 10## 简介 11近场通信(Near Field Communication,NFC)是一种短距高频的无线电技术,在13.56MHz频率运行,通信距离一般在10厘米距离内。电子设备可以通过NFC通信技术和NFC标签通信,从标签中读取数据,或写入数据到标签。<br> 12NFC标签支持一种或多种通信技术,具体技术如下: 13- NfcA (也称为 ISO 14443-3A) 14- NfcB (也称为 ISO 14443-3B) 15- NfcF (也称为 JIS 6319-4) 16- NfcV (也称为 ISO 15693) 17- IsoDep (也称为 ISO 14443-4) 18- NDEF 19- MifareClassic 20- MifareUltralight 21 22## 场景介绍 23电子设备通过NFC天线位置触碰NFC标签卡片,完成NFC标签卡片的读取或写入。从使用场景上,可以分成NFC标签前台读写和NFC标签后台读写。 24- NFC标签前台读写<br> 25前台读写是指在触碰NFC标签之前,用户先在电子设备上打开特定的应用程序,用户明确想使用所打开的应用程序和NFC标签进行读写操作。用户打开应用程序在前台,并且进入应用的刷卡页面之后,电子设备触碰NFC标签,只会把读取到的卡片分发给前台应用。 26- NFC标签后台读写<br> 27后台读写是指不打开特定的NFC标签应用程序,电子设备触碰发现NFC标签后,根据NFC标签的技术类型,分发给能够处理的应用程序。如果能匹配到多个应用程序,则弹出应用选择器列举出应用列表给用户手动选择。用户选择指定的应用后,自动跳转到应用程序的NFC标签读写卡页面。 28- 标签读写约束条件<br> 29不管是前台读写,还是后台读写,电子设备能够发现NFC标签的前提条件是设备必须是亮屏和解锁状态。 30 31## 接口说明 32 33NFC标签读写完整的JS API说明以及实例代码请参考:[NFC标签接口](../../reference/apis-connectivity-kit/js-apis-nfcTag.md)。 34 35获取不同技术类型标签对象的接口说明如下表,根据不同技术的标签对象来执行NFC标签的读写。 36 37| 接口名 | 功能描述 | 38| ---------------------------------- | ------------------------------------------------------------------------------ | 39| getNfcA(tagInfo: TagInfo): NfcATag | 获取NfcA技术类型的标签对象。 | 40| getNfcB(tagInfo: TagInfo): NfcBTag | 获取NfcB技术类型的标签对象。 | 41| getNfcF(tagInfo: TagInfo): NfcFTag | 获取NfcF技术类型的标签对象。 | 42| getNfcV(tagInfo: TagInfo): NfcVTag | 获取NfcV技术类型的标签对象。 | 43| getIsoDep(tagInfo: TagInfo): IsoDepTag | 获取IsoDep技术类型的标签对象。 | 44| getNdef(tagInfo: TagInfo): NdefTag | 获取NDEF技术类型的标签对象。 | 45| getMifareClassic(tagInfo: TagInfo): MifareClassicTag | 获取MifareClassic技术类型的标签对象。 | 46| getMifareUltralight(tagInfo: TagInfo): MifareUltralightTag | 获取MifareUltralight技术类型的标签对象。 | 47 48## 开发准备 49 50### NFC标签前台读写或后台读写的选择 51NFC标签读写应用开发者根据业务需要,可以选择实现前台读卡或者后台读卡。两种不同的读卡方式,代码实现上会存在一些差异。 52- NFC标签前台读写<br> 531. 在配置文件module.json5中,不需要静态声明过滤读取NFC标签的技术类型,而是通过[tag.registerForegroundDispatch](../../reference/apis-connectivity-kit/js-apis-nfcTag.md#tagregisterforegrounddispatch10)或者[tag.on](../../reference/apis-connectivity-kit/js-apis-nfcTag.md#tagon11)来完成动态注册。 542. 通过registerForegroundDispatch或on来动态注册前台读写标签时,入参中必须指定需要读取NFC标签的技术类型。 553. 如果选择registerForegroundDispatch注册,当应用运行在前台并进入该页面,NFC的卡模拟功能在打开时,可以同时完成刷卡。如果选择tag.on注册,当应用运行在前台并进入该页面时,NFC的卡模拟是关闭的,无法同时进行刷卡功能。 564. 当应用页面切换到后台时,需要显式调用[tag.unregisterForegroundDispatch](../../reference/apis-connectivity-kit/js-apis-nfcTag.md#tagunregisterforegrounddispatch10)或者[tag.off](../../reference/apis-connectivity-kit/js-apis-nfcTag.md#tagoff11)来取消注册,退出前台读卡优先功能。 57- NFC标签后台读写<br> 581. 在配置文件module.json5中,需要静态声明过滤读取NFC标签的技术类型。根据业务需要至少定义一种读标签的技术类型,‘tag-tech/’是前缀,后面跟着技术类型描述。 592. 技术类型的描述字符,必须完整匹配并区分大小写,需要严格匹配。 60 61> **注意:** 62> - 从API version 9之后的应用开发新增支持Stage模型,作为目前主推并长期演进的模型。 63> - NFC标签读写示例代码的提供,全部按照Stage模型来说明。 64 65## 开发步骤 66 67### 前台读取标签 681. 在module.json5文件中声明NFC标签读取的权限,以及声明NFC标签特定的action。 692. import需要的tag模块和其他相关的模块。 703. 判断设备是否支持NFC能力。 714. 调用tag模块中前台优先的接口,使能前台应用程序优先处理所发现的NFC标签功能。 725. 获取特定技术类型的NFC标签对象。 736. 执行读写接口完成标签数据的读取或写入数据到标签。 747. 退出应用程序NFC标签页面时,调用tag模块退出前台优先功能。 75 76```ts 77 "abilities": [ 78 { 79 "name": "EntryAbility", 80 "srcEntry": "./ets/entryability/EntryAbility.ts", 81 "description": "$string:EntryAbility_desc", 82 "icon": "$media:icon", 83 "label": "$string:EntryAbility_label", 84 "startWindowIcon": "$media:icon", 85 "startWindowBackground": "$color:start_window_background", 86 "exported": true, 87 "skills": [ 88 { 89 "entities": [ 90 "entity.system.home" 91 ], 92 "actions": [ 93 "ohos.want.action.home", 94 95 // actions必须包含"ohos.nfc.tag.action.TAG_FOUND" 96 "ohos.nfc.tag.action.TAG_FOUND" 97 ] 98 } 99 ] 100 } 101 ], 102 "requestPermissions": [ 103 { 104 // 添加NFC标签操作的权限 105 "name": "ohos.permission.NFC_TAG", 106 "reason": "$string:app_name", 107 } 108 ] 109``` 110 111```ts 112import { tag } from '@kit.ConnectivityKit'; 113import { BusinessError } from '@kit.BasicServicesKit'; 114import { hilog } from '@kit.PerformanceAnalysisKit'; 115import { AbilityConstant, UIAbility, Want, bundleManager } from '@kit.AbilityKit'; 116 117let nfcTagElementName: bundleManager.ElementName; 118let foregroundRegister: boolean; 119 120async function readerModeCb(error : BusinessError, tagInfo : tag.TagInfo) { 121 if (!error) { 122 // 获取特定技术类型的NFC标签对象 123 if (tagInfo == null) { 124 hilog.error(0x0000, 'testTag', 'readerModeCb tagInfo is invalid'); 125 return; 126 } 127 if (tagInfo.uid == null) { 128 hilog.error(0x0000, 'testTag', 'readerModeCb uid is invalid'); 129 return; 130 } 131 if (tagInfo.technology == null || tagInfo.technology.length == 0) { 132 hilog.error(0x0000, 'testTag', 'readerModeCb technology is invalid'); 133 return; 134 } 135 136 // 标签里面可能支持多种技术类型,选择特定的技术类型接口,完成标签数据的读取或写入 137 // 下面示例代码,使用IsoDep完成标签数据的读取或写入 138 let isoDep : tag.IsoDepTag | null = null; 139 for (let i = 0; i < tagInfo.technology.length; i++) { 140 if (tagInfo.technology[i] == tag.ISO_DEP) { 141 try { 142 isoDep = tag.getIsoDep(tagInfo); 143 } catch (error) { 144 hilog.error(0x0000, 'testTag', 'readerModeCb getIsoDep error = %{public}s', JSON.stringify(error)); 145 return; 146 } 147 } 148 // 也可以按需选择其它类型的技术读写标签 149 } 150 if (isoDep == null) { 151 hilog.error(0x0000, 'testTag', 'readerModeCb getIsoDep is invalid'); 152 return; 153 } 154 155 // 使用IsoDep技术连接到NFC标签 156 try { 157 isoDep.connect(); 158 } catch (error) { 159 hilog.error(0x0000, 'testTag', 'readerModeCb isoDep.connect() error = %{public}s', JSON.stringify(error)); 160 return; 161 } 162 if (!isoDep.isConnected()) { 163 hilog.error(0x0000, 'testTag', 'readerModeCb isoDep.isConnected() false.'); 164 return; 165 } 166 167 // 发送指令到已连接的标签,获取标签的响应数据 168 let cmdData = [0x01, 0x02, 0x03, 0x04]; // 修改为正确的访问标签的指令数据 169 try { 170 isoDep.transmit(cmdData).then((response : number[]) => { 171 hilog.info(0x0000, 'testTag', 'readerModeCb isoDep.transmit() response = %{public}s.', JSON.stringify(response)); 172 }).catch((err : BusinessError)=> { 173 hilog.error(0x0000, 'testTag', 'readerModeCb isoDep.transmit() err = %{public}s.', JSON.stringify(err)); 174 return; 175 }); 176 } catch (businessError) { 177 hilog.error(0x0000, 'testTag', 'readerModeCb isoDep.transmit() businessError = %{public}s.', JSON.stringify(businessError)); 178 return; 179 } 180 } else { 181 hilog.info(0x0000, 'testTag', 'readerModeCb readerModeCb error %{public}s', JSON.stringify(error)); 182 } 183} 184 185export default class EntryAbility extends UIAbility { 186 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 187 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 188 189 // 判断设备是否支持NFC能力 190 if (!canIUse("SystemCapability.Communication.NFC.Core")) { 191 hilog.error(0x0000, 'testTag', 'nfc unavailable.'); 192 return; 193 } 194 195 // 根据应用程序信息,初始化正确的值 196 nfcTagElementName = { 197 bundleName: want.bundleName ?? '', 198 abilityName: want.abilityName ?? '', 199 moduleName: want.moduleName, 200 } 201 } 202 203 onForeground() { 204 // 应用进入前台,调用tag模块中前台优先的接口,使能前台应用程序优先处理所发现的NFC标签功能 205 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); 206 if (nfcTagElementName != undefined) { 207 // 根据业务需要,选择需要读取标签的通信技术 208 let techList : number[] = [tag.NFC_A, tag.NFC_B, tag.NFC_F, tag.NFC_V]; 209 try { 210 tag.on('readerMode', nfcTagElementName, techList, readerModeCb); 211 foregroundRegister = true; 212 } catch (error) { 213 hilog.error(0x0000, 'testTag', 'on readerMode error = %{public}s', JSON.stringify(error)); 214 } 215 } 216 } 217 218 onBackground() { 219 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); 220 // 退出应用程序NFC标签页面时,调用tag模块退出前台优先功能 221 if (foregroundRegister) { 222 foregroundRegister = false; 223 try { 224 tag.off('readerMode', nfcTagElementName); 225 } catch (error) { 226 hilog.error(0x0000, 'testTag', 'off readerMode error = %{public}s', JSON.stringify(error)); 227 } 228 } 229 } 230} 231``` 232 233### 后台读取标签 2341. 在module.json5文件中声明NFC标签读取的权限,声明NFC标签特定的action,以及声明本应用程序的能够处理的NFC标签技术类型。 2352. import需要的tag模块和其他相关的模块。 2363. 获取特定技术类型的NFC标签对象。 2374. 执行读写接口完成标签数据的读取或写入数据到标签。 238 239```ts 240 "abilities": [ 241 { 242 "name": "EntryAbility", 243 "srcEntry": "./ets/entryability/EntryAbility.ts", 244 "description": "$string:EntryAbility_desc", 245 "icon": "$media:icon", 246 "label": "$string:EntryAbility_label", 247 "startWindowIcon": "$media:icon", 248 "startWindowBackground": "$color:start_window_background", 249 "exported": true, 250 "skills": [ 251 { 252 "entities": [ 253 "entity.system.home" 254 ], 255 "actions": [ 256 "ohos.want.action.home", 257 258 // actions必须包含"ohos.nfc.tag.action.TAG_FOUND" 259 "ohos.nfc.tag.action.TAG_FOUND" 260 ], 261 262 // 根据业务需要至少定义一种读标签的技术类型,‘tag-tech/’是前缀,后面跟着技术类型描述 263 "uris": [ 264 { 265 "type":"tag-tech/NfcA" 266 }, 267 { 268 "type":"tag-tech/IsoDep" 269 } 270 // 必要时可添加其他技术类型 271 // 例如: NfcB/NfcF/NfcV/Ndef/MifareClassic/MifareUL/NdefFormatable 272 ] 273 } 274 ] 275 } 276 ], 277 "requestPermissions": [ 278 { 279 // 添加NFC标签操作的权限 280 "name": "ohos.permission.NFC_TAG", 281 "reason": "$string:app_name", 282 } 283 ] 284``` 285 286```ts 287import { tag } from '@kit.ConnectivityKit'; 288import { BusinessError } from '@kit.BasicServicesKit'; 289import { hilog } from '@kit.PerformanceAnalysisKit'; 290import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 291 292export default class EntryAbility extends UIAbility { 293 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 294 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 295 296 // 获取特定技术类型的NFC标签对象 297 let tagInfo : tag.TagInfo; 298 try { 299 tagInfo = tag.getTagInfo(want); 300 } catch (error) { 301 hilog.error(0x0000, 'testTag', 'getTagInfo error = %{public}s', JSON.stringify(error)); 302 return; 303 } 304 305 if (tagInfo == null) { 306 hilog.error(0x0000, 'testTag', 'tagInfo is invalid'); 307 return; 308 } 309 if (tagInfo.uid == null) { 310 hilog.error(0x0000, 'testTag', 'uid is invalid'); 311 return; 312 } 313 if (tagInfo.technology == null || tagInfo.technology.length == 0) { 314 hilog.error(0x0000, 'testTag', 'technology is invalid'); 315 return; 316 } 317 318 // 标签里面可能支持多种技术类型,选择特定的技术类型接口,完成标签数据的读取或写入 319 // 下面示例代码,使用IsoDep完成标签数据的读取或写入 320 let isoDep : tag.IsoDepTag | null = null; 321 for (let i = 0; i < tagInfo.technology.length; i++) { 322 if (tagInfo.technology[i] == tag.ISO_DEP) { 323 try { 324 isoDep = tag.getIsoDep(tagInfo); 325 } catch (error) { 326 hilog.error(0x0000, 'testTag', 'getIsoDep error = %{public}s', JSON.stringify(error)); 327 return; 328 } 329 } 330 // 也可以按需选择其它类型的技术读写标签 331 } 332 if (isoDep == null) { 333 hilog.error(0x0000, 'testTag', 'getIsoDep is invalid'); 334 return; 335 } 336 337 // 使用IsoDep技术连接到NFC标签 338 try { 339 isoDep.connect(); 340 } catch (error) { 341 hilog.error(0x0000, 'testTag', 'isoDep.connect() error = %{public}s', JSON.stringify(error)); 342 return; 343 } 344 if (!isoDep.isConnected()) { 345 hilog.error(0x0000, 'testTag', 'isoDep.isConnected() false.'); 346 return; 347 } 348 349 // 发送指令到已连接的标签,获取标签的响应数据 350 let cmdData = [0x01, 0x02, 0x03, 0x04]; // 修改为正确的访问标签的指令数据 351 try { 352 isoDep.transmit(cmdData).then((response : number[]) => { 353 hilog.info(0x0000, 'testTag', 'isoDep.transmit() response = %{public}s.', JSON.stringify(response)); 354 }).catch((err : BusinessError)=> { 355 hilog.error(0x0000, 'testTag', 'isoDep.transmit() err = %{public}s.', JSON.stringify(err)); 356 return; 357 }); 358 } catch (businessError) { 359 hilog.error(0x0000, 'testTag', 'isoDep.transmit() businessError = %{public}s.', JSON.stringify(businessError)); 360 return; 361 } 362 } 363} 364```