NFC相信大家都很熟悉,现实中经常使用的门禁卡,公交卡,地铁卡,饭卡等都是采用NFC功能,那么你知道吗,NFC也可以用微信小程序来实现。使用微信小程序可以读取/写入让手机成为一个刷卡器,也可以使用微信小程序模拟一个主机卡,来刷开门禁/饭卡等等。本章就带大家来一起看看微信小程序的NFC有何不同!

目录

一、什么是NFC

二、NFC可以做什么

三、微信小程序的NFC

四、使用步骤

1.研究API

2.使用方法

一:新项目

二:设置简单页面及对应的js

三:根据上述我们理清的 NFC生命周期顺序来搭建我们的NFC项目。

四:需要开始初始化StartHCE(初始化NFC,将手机初始化为一个主机模拟卡)

五:onHCEMEssage 监听

六:使用wx.sendHCEMessage 发送NFC消息

总结


一、什么是NFC

NFC是一种采用13.56MHz频带的近距离无线通讯技术,虽然通讯距离仅为10cm左右,不过和非接触式IC卡技术一样,我们只需要“触碰一下”即可在不同的电子产品之间交换数据。

与非接触IC卡不同,NFC与非接触式IC卡不同,NFC可进行双向通信。只要是支持NFC的产品和IC卡,就可以读出或写入数据。还可在手机等便携产品间进行通信。数据传输速度不高,有106kbit/秒、212kbit/秒、424kbit/秒以及848kbit/秒四种速度可供选择。

NFC介绍

二、NFC可以做什么

NFC具有“卡模拟”、“读写器模拟”以及“产品间通信(P2P)”三种功能。

1. 卡模拟:举例来说,我们可以用手机来模拟门禁卡,公交卡,饭卡等等。但是要注意的是,它是模拟出来的,也就是说并不实体存在的一个卡。

2. 读写器模拟:指的就是,你可以模拟一个读写器,别人用NFC卡来你这里刷卡,你就可以获取其卡片上的信息。

3. 通讯讲就是数据通信,使用NFC卡在刷的时候都会进行数据交换。用来做一些事情,比如:配置网络,设置信息,传输文件等等。

三、微信小程序的NFC

NFC | 微信开放文档

我们打开文档可以看到,对于NFC,微信给它的名字叫:近场通讯

首先一定要强调的是,因为苹果手机权限的缘故,所以NFC在微信小程序中只支持:Android

微信将NFC分为了三部分

  • HCE(基于主机的卡模拟),也就是将安卓手机模拟成实体的智能卡。我们可以通过模拟的智能卡来刷对应的读卡器,给读卡器传递数据。
  • 支持NFC读写,也就是将手机作为读卡器来使用。也就是我们可以将一些实体的NFC卡通过贴在手机上从而实现读取卡内容。
  • NFC标签打开小程序。指的就是我们可以通过NFC卡片触碰手机,快速唤起小程序页面的能力。

1. HCE(基于主机的卡模拟)的使用场景:

  • 使用手机做门禁卡/公交卡/地铁卡
  • 使用手机给读卡器传递数据(配网、登记)

2. NFC读写的使用场景:

  • 使用手机做刷卡器,来获取NFC卡的信息。

3. NFC标签打开小程序的使用场景

  • 设备的快速配网
  • 文件快速传输等快捷控制

官网文档写的也很齐全了,大家可以根据自己的需求选择不同的场景来进行使用。

本章我就选择了HCE(基于主机的卡模拟)场景。

四、使用步骤

1.研究API

wx.stopHCE(Object object) | 微信开放文档

微信官方文档中,卡模拟一共有六个API。

分别为:

wx.stopHCE 关闭NFC模块。

wx.startHCE 初始化NFC

wx.sendHCEMessage 发送NFC消息

wx.onHCEMessage  监听接收NFC设备消息事件。

wx.offHCEMessage  移除接收 NFC 设备消息事件的监听函数

wx.getHCEState 判断当前设备是否支持 HCE 能力。

再次特别要强调的是,NFC仅在Android系统下支持。

2.使用方法

有过开发经验的同学其实比较清楚。

以上API 很明显就是一个生命周期

从开始到销毁。我们该如何去操作这个NFCapi呢?

1. 首先调用 wx.getHCEState, 判断设备是否支持NFC

2. 调用 wx.startHCE(OBJECT) 初始化手机的NFC模块;

3. 初始化完成后,调用 wx.onHCEMessage 监听芯片响应的消息;

4. 点击页面上的“询卡”按钮,调用 wx.sendHCEMessage发送询卡指令;

5. 这时 wx.onHCEMessage(应该可以收到带有uid信息的芯片响应数据;

6. 业务处理。

7.  全部操作完成后之后,调用 wx.stopHCE停止手机的NFC模块

好了,废话不多说,直接进入我们的正式项目。

一:新项目

我们需要利用微信开发者工具,来新建一个我们的项目。APPID大家如果有的话就用自己的,没有的话则使用测试号即可。

二:设置简单页面及对应的js

三:根据上述我们理清的 NFC生命周期顺序来搭建我们的NFC项目。

顺序自然为:页面打开初始化的时候就需要 getHCEState 判断设备是否支持NFC。不支持则需要做兼容。

当返回值的errCode为 0  的时候则一切正常,是支持NFC的。

同时,为了更有效的避免大家判断code,所以我们可以将官网的 getHCEState返回code 声明出来,以便后面使用。

四:需要开始初始化StartHCE(初始化NFC,将手机初始化为一个主机模拟卡)

wx.startHCE接收一个参数为 aid_list。

官方解释意思为,需要注册到系统AID列表中,AID列表其实就是每个刷卡器的唯一标识,不清楚的就问下你们的安卓或者默认填写:F22222222。与其它API 类似,它也有自己的返回值,默认0为正常成功的。

五:onHCEMEssage 监听

在说这里之前,网上许多朋友碰到的类似,wx.onHCEMessage,不管怎么调都没有返回值?

不知道有没有伙伴仔细看我上面的话,初始化完成后,onHCEMessage其实就要开始监听。至于为什么没有返回值。是因为,wx.onHCEMessage是一个监听。它需要读卡器来响应它,也就是说要给需要读卡器(设备)通过render 指令 给它发消息,这时候它才能拿得到值。

它有三个返回值,但是我们只一般取第一个也就是 messageType。

messageType的值 = 1 时,也就是说,读卡器给我们响应了,说它接收到了NFC。这时候我们就可以调用 sendHCEMessage 来给读卡器发送消息了。

messageType的值 = 2 时,就是说,手机已经从读卡器上拿开了。

所以我们在这里只判断 messageType的值为1. 当等于1 的时候我们就可以开始发消息了。

六:使用wx.sendHCEMessage 发送NFC消息

细心的朋友会发现,发个消息不就是 wx.sendHCEMessage就够了。为啥我的有一大堆的ArrayBuffer 甚至还有comm.

这是因为文档有标注:

wx.sendHCEMessage 的data 必须是一个二进制数据。也就是我们不能将普通的JSON,字符串等数据 传递给它。否则会报错。

那这时候我们需要怎么办呢?

转换!

我在项目中的comm 就是一个封装的 转换文件。内容如下:

comm.js

const formatTime = date => {const year = date.getFullYear()const month = date.getMonth() + 1const day = date.getDate()const hour = date.getHours()const minute = date.getMinutes()const second = date.getSeconds()return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}
/*** 生成指定长度随机数*/
function genRandom(n) {let a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; //生成的随机数的集合  let res = [];for (let i = 0; i < n; i++) {let index = parseInt(Math.random() * (a.length));    //生成一个的随机索引,索引值的范围随数组a的长度而变化  res.push(a[index]);a.splice(index, 1)  //已选用的数,从数组a中移除, 实现去重复  }return res.join('');
}
/*** 字符串转换为时间* @param  {String} src 字符串*/
function strToDate(dateObj){dateObj = dateObj.replace(/T/g, ' ').replace(/\.[\d]{3}Z/, '').replace(/(-)/g, '/')dateObj = dateObj.slice(0, dateObj.indexOf("."))return new Date(dateObj)
}
function isFunctinMethod(name) {if (name != undefined && typeof name === 'function') {return true}return false
}
const formatNumber = n => {n = n.toString()return n[1] ? n : '0' + n
}// ArrayBuffer转16进度字符串
function ab2hex(buffer) {var hexArr = Array.prototype.map.call(new Uint8Array(buffer),function (bit) {return ('00' + bit.toString(16)).slice(-2)})return hexArr.join('');
}//十六进制字符串转字节数组
function hex2Bytes(str) {var pos = 0;var len = str.length;if (len % 2 != 0) {return null;}len /= 2;var hexA = new Array();for (var i = 0; i < len; i++) {var s = str.substr(pos, 2);var v = parseInt(s, 16);hexA.push(v);pos += 2;}return hexA;
}
function hex2ArrayBuffer(hex){var pos = 0;var len = hex.length;if (len % 2 != 0) {return null;}len /= 2;var buffer = new ArrayBuffer(len)var dataview=new DataView(buffer)for (var i = 0; i < len; i++) {var s = hex.substr(pos, 2);var v = parseInt(s, 16);dataview.setInt16(i,v)pos += 2;}return buffer
}
/*** string转16进制*/
function stringToHex(str) {var val = "";for (var i = 0; i < str.length; i++) {if (val == "")val = str.charCodeAt(i).toString(16);elseval += str.charCodeAt(i).toString(16);}return val;
}
/*** 16进制转string*/
function hexCharCodeToStr(hexCharCodeStr) {var trimedStr = hexCharCodeStr.trim();var rawStr =trimedStr.substr(0, 2).toLowerCase() === "0x"?trimedStr.substr(2):trimedStr;var len = rawStr.length;if (len % 2 !== 0) {alert("Illegal Format ASCII Code!");return "";}var curCharCode;var resultStr = [];for (var i = 0; i < len; i = i + 2) {curCharCode = parseInt(rawStr.substr(i, 2), 16); // ASCII Code ValueresultStr.push(String.fromCharCode(curCharCode));}return resultStr.join("");
}function pad(num, n) {var len = num.toString().length;while (len < n) {num = "0" + num;len++;}return num;
}function strToHexCharCode(str) {if (str === "")return "";var hexCharCode = [];hexCharCode.push("0x");for (var i = 0; i < str.length; i++) {hexCharCode.push((str.charCodeAt(i)).toString(16));}return hexCharCode.join("");
}
/*** string转byte数组*/
function stringToByteArray(str) {var bytes = new Array();var len, c;len = str.length;for (var i = 0; i < len; i++) {c = str.charCodeAt(i);if (c >= 0x010000 && c <= 0x10FFFF) {bytes.push(((c >> 18) & 0x07) | 0xF0);bytes.push(((c >> 12) & 0x3F) | 0x80);bytes.push(((c >> 6) & 0x3F) | 0x80);bytes.push((c & 0x3F) | 0x80);} else if (c >= 0x000800 && c <= 0x00FFFF) {bytes.push(((c >> 12) & 0x0F) | 0xE0);bytes.push(((c >> 6) & 0x3F) | 0x80);bytes.push((c & 0x3F) | 0x80);} else if (c >= 0x000080 && c <= 0x0007FF) {bytes.push(((c >> 6) & 0x1F) | 0xC0);bytes.push((c & 0x3F) | 0x80);} else {bytes.push(c & 0xFF);}}return bytes;
}
/*** byte数组转string*/
function byteToString(bytearr) {if (typeof arr === 'string') {return arr;}var str = '',_arr = arr;for (var i = 0; i < _arr.length; i++) {var one = _arr[i].toString(2),v = one.match(/^1+?(?=0)/);if (v && one.length == 8) {var bytesLength = v[0].length;var store = _arr[i].toString(2).slice(7 - bytesLength);for (var st = 1; st < bytesLength; st++) {store += _arr[st + i].toString(2).slice(2);}str += String.fromCharCode(parseInt(store, 2));i += bytesLength - 1;} else {str += String.fromCharCode(_arr[i]);}}return str;
}
/*** 二进制转10*/
function bariny2Ten(byte){return parseInt(byte, 2)
}
function bariny2Hex(a){return parseInt(a, 16)
}
/*** 10/16进制转2进制*/
function ten2Bariny(ten){return ten.toString(2)
}
function str2Hex(str){return parseInt(str, 10).toString(16)
}
/*** 16进制转2进制*/
function hex2bariny(hex){return parseInt(hex, 16).toString(2)
}
module.exports = {formatTime: formatTime,isFunctinMethod: isFunctinMethod,ab2hex: ab2hex,hex2Bytes: hex2Bytes,stringToByteArray: stringToByteArray,byteToString: byteToString,hex2ArrayBuffer: hex2ArrayBuffer,bariny2Ten: bariny2Ten,bariny2Hex: bariny2Hex,ten2Bariny: ten2Bariny,str2Hex: str2Hex,hex2bariny: hex2bariny,genRandom: genRandom,stringToHex: stringToHex,hexToString: hexCharCodeToStr,pad: pad
}

它里面包括了 string 转 字节数组等常用转换方法。

至于 ArrayBuffer  以及 DataView 等方法,相信各位也都知道,就不在这里做详解。不了解的可以在CSDN上搜索学习。

由于我在这里给后台需要传递的是一个JSON,所以我就将JSON转换为字符串。然后再将字符串转换为字节数组,传递给了消息。

comm就是 上述封装的转化文件。comm.js

stringToByteArray 方法就是 comm.js 文件中封装的 string转 字节数组的方法。

当我们完成这一切后使用“真机调试”前往对应刷卡器跟前刷卡即可!会发现前端已经成功了,并且读卡器也已经在控制台上打印出了我们传递的数据。

至此,我们的项目就结束了。

总结

以上就是今天分享给大家的微信小程序HCE模拟主机卡的功能。

但是其中不缺乏有一些坑。我列出来供大家参考!

1. wx.onHCEMessage 没有返回值。

这个可能是我见过最多的问题了,其实它没有返回值的原因就是,读卡器没有 返回信息。通俗点讲就是,你手机开启了NFC,当你挨着读卡器的时候,手机不知道你有没有挨上读卡器,所以需要读卡器给你说一句,你挨上我了,可以发消息了。所以这块的处理是需要对应的客户端开发来处理的,小程序端已经结束了。如果客户端开发不会的话,在此处放一个大佬的文章,可以让客户端开发参考下。微信小程序 NFC HCE卡模拟+AndroidNFC读取优必果

2. 我的手机刷卡没反应。是我哪里写错了吗?

如果你是按照上面的步骤同时结合官网文档来进行的,那么就基本没错。如果刷卡没反应,并且这时候客户端已经是处理好了的。那么只有一个可能,手机兼容性不够。你换个安卓手机试试。(PS:我试了 vivo跟华为。vivo 有两个手机不支持。华为目前使用的一款是支持的)、

3. aid_list 是什么,怎么找呢?

其实最简单的就是找你们客户端开发,他们有办法找到你的读卡器的 aid, 默认一般都是F222222222 。如果不是那就找一找你们的客户端开发,让他帮你找一下。

好了,文章就到这里。后面大家若是在开发过程有疑问,欢迎提问、私信我。

微信小程序接入NFC,使用HCE模拟主机卡完成NFC刷卡发送消息相关推荐

  1. 微信支持环信_环信客户互动云v5.39已发布:支持微信小程序接入

    环信客户互动云v5.39_产品更新说明 发布日期:2018-11-06 客服模式 质检中新增会话ID字段 质检中新增会话ID字段,与历史会话中的会话ID对应,支持根据会话ID搜索质检会话,以及在质检详 ...

  2. 微信小程序接入第三方插件腾讯位置服务地图选点

    微信小程序接入第三方插件腾讯位置服务地图选点 1.在小程序服务平台中添加"腾讯位置服务地图选点"插件 1.在小程序服务平台中添加"腾讯位置服务地图选点"插件 微 ...

  3. uni-app 微信小程序接入高德SDK

    uni-app 微信小程序接入高德SDK 参考文档:https://lbs.amap.com/api/wx/gettingstarted 一.获取高德Key 配置高德key 二.获取高德key的操作步 ...

  4. 【物联网】微信小程序接入阿里云物联网平台

    微信小程序接入阿里云物联网平台 一 阿里云平台端 1.登录阿里云 阿里云物联网平台 点击进入公共实例,之前没有的点进去申请 2.点击产品->创建产品 3.产品名称自定义,按项目选择类型,节点类型 ...

  5. 微信小程序接入关联微信公众号official-account方案总结

    微信小程序接入关联微信公众号official-account方案总结 需求描述: 最近在小程序项目中有这样的需求,在微信小程序里面显示出关联的微信公众号,让用户在小程序一步直达公众号. 解决方案: 第 ...

  6. 微信小程序接入emq

    微信小程序接入emq 环境 emq v2.3.0 lnmp 微信开发工具 ubutun 16.04 1. 下载demo 微信小程序 MQTT 接入Demo下载地址 下载完成后,打开工程,修改index ...

  7. 微信小程序接入微信支付(二):后台调用统一下单接口

    微信统一支付官方文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1 因该接口需要商户系统中自己的订单编号,笔者先 ...

  8. 微信小程序接入腾讯云IM即时通讯(获取聊天历史记录开发步骤)

    微信小程序接入腾讯云IM即时通讯(获取聊天历史记录开发步骤) 1.先看文档: 获取 C2C 历史消息 :https://cloud.tencent.com/document/product/269/1 ...

  9. 微信小程序接入微信支付(三):小程序端调用支付接口

    微信小程序调用支付接口官方文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5 在上一节中 ...

  10. 微信小程序--sha1加密元素以字典顺序排序微信小程序接入微信公众平台

    java--sha1加密 算法实现 bytes[i] & 0xFF 原理详解 以字典顺序排序 php----$_GET[] http认证中的nonce与timestamp token和nonc ...

最新文章

  1. 作为一个Linux/Unix程序员有哪些要求
  2. 算法导论Java实现-随机化数组的两种方式(5.3章节)
  3. 3名游客在长城墙体上刻字,八达岭长城:已会同公安部门调查取证
  4. 计算机软件基础02243知识点,02243计算机软件基础(一) 历年真题
  5. POJ 327820493083
  6. 使用混合多云每个人都应避免的3个陷阱(第1部分)
  7. idea创建springboot项目出现的问题
  8. 一個便宜的高负载网站架构
  9. 【再探backbone 02】集合-Collection
  10. 基于Arweave的文件同步应用ArDrive完成160万美元融资
  11. python字符串驻留机制_python的内存驻留机制(小数据池)
  12. 江门计算机职称考试时间,江门职称计算机考试时间
  13. 网页源代码怎么屏蔽?
  14. 【IDEA】IDEA 格式化 代码技巧 idea 格式化 会加 <p> 标签
  15. 自定义的毛玻璃效果,高斯模糊
  16. 如何解决Gridea部分主题不渲染Katex的问题
  17. RDKit|分子3D构象生成与优化
  18. SAN存储和服务器虚拟化安装方案,如何部署SAN
  19. 深信服 2021 面试总结
  20. 白嫖党最爱!java中break跳出多层循环

热门文章

  1. 普通计算机光驱可以读多少尺寸的光盘,光驱内一旦有光盘,计算机启动时要有很长的读盘时间...
  2. 有一种密码学专用语言叫做ASN.1
  3. DVDFab Player Ultra v6.1.1.4 4K蓝光媒体播放器
  4. 手机内存卡转化linux,安卓手机内置内存卡和外置内存卡(SD卡)互换方法
  5. java做视频模板_用手机怎么制作钻石雨视频?清爽视频编辑套用模板新技巧!...
  6. 华为云大数据解决方案赋能金融行业发展,打造5G智慧银行营业厅
  7. Eclipse闪退修复
  8. 利用Debug调试代码解决0xC0000005: 读取位置 0x0000000000000000 时发生访问冲突
  9. 品牌“潮”营销​:Z世代成为消费新主力,我国潮牌营销洞察报告​(下)
  10. WebDriver·TestNg学习日志(Java/Ruby/.Net版)