记录使用微信小程序的NFC和蓝牙功能读取15693芯片的开发历程

时间:2022-05-12 16:00:46

开发目标:

(1) 对于Android手机,直接通过微信小程序调用手机的NFC功能,对15693协议的芯片进行读写操作;

(2)对于苹果手机(及没有NFC模块的手机),通过微信小程序的蓝牙功能连接到蓝牙/NFC读写器,然后通过蓝牙发送指令操作读写器对15693协议的芯片进行读写操作。

DAY #1

上午开了半天会,下午开始开发。

先开发简单的:直接通过Android手机的NFC模块读写芯片。开发思路如下:

1. 首先调用 wx.getHCEState(OBJECT), 判断设备是否支持NFC,如果不支持就走蓝牙通道;

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

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

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

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

6. 根据uid发送select指令,以及后续多个指令;

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

8. 完成。

思路很清晰,并且开发思路中需要用到的每一个NFC的接口都有对应文档,应该是没有什么问题了,接着就开始开发NFC模块了。

第一步、微信小程序基本框架搭建,非常顺利的完成界面,调试模式等。

然后开始写nfc模块,首先是 wx.getHCEState(OBJECT),比较顺利,同时测试了一下没有NFC模块的手机/NFC为开启等多种情况的返回值(因为小程序开发文档没有写啊)。

然后是初始化NFC模块,也比较顺利。

然后完成wx.onHCEMessage(CALLBACK)消息监听,然后就是点击按钮发送消息  wx.sendHCEMessage(OBJECT),从这时开始就开始进入无助的状态。

尝试了多组数据进行发送,始终不能触发 wx.onHCEMessage, 折腾了半个小时,未果,等第二天解决吧。

DAY #2

首先仍然怀疑是数据格式不对,因为第一次开发跟芯片相关的程序,并且也是第一次接触15693协议,所以一开始非常坚定的相信是自己组装的发送数据不对,然后就去请教了芯片开发大牛,以及把15693协议的相关部分读了大约10遍,在网上找15693协议相关资料,百度微信小程序NFC开发资料。

还是不能触发 wx.onHCEMessage。

直到在微信小程序开发社区,输入关键字“wx.onHCEMessage”,然后从寥寥的几篇post中,终于明白:TMD微信小程序的NFC功能仅仅是把手机模拟成一张芯片卡,而不是把手机当做芯片读写器!WTF

这时已经下午3点。

因为我一开始跟公司领导说的是微信小程序可以直接调NFC读取芯片,这下就尴尬了,对项目的规划影响巨大。然后,抱着一丝恐惧,也抱着一丝希望,多方搜索,多方确认,TMD微信小程序目前确实只能把手机模拟成一张芯片卡。

然后跟公司领导电话里简单汇报了一下,领导说第二天开会讨论。

然后开始写蓝牙通道的解决方案。

微信小程序开放的蓝牙接口如下:

记录使用微信小程序的NFC和蓝牙功能读取15693芯片的开发历程

共18个接口,实在是太多了。这里不得不吐槽一下微信小程序的架构师们,你们设计的开发文档太粗糙了,需要开发人员写大量的代码才能完成一些基本功能,比如我自己想要完成的一些功能:

1. 初始化蓝牙模块;

2. 打开/关闭蓝牙发现;

3. 随时只允许单一设备连接,不允许多设备连接;

4. 已连接上的蓝牙断开后自动重连,自动重连失败后提示并继续自动重连;

5. 每次连上蓝牙设备后自动记住蓝牙设备ID,并写入一个长度为5的数组,并写到storage里面,这样下次进入小程序后就可以自动连接曾经连过的蓝牙设备。

下面是我希望自己的程序可以这样调用蓝牙模块的伪代码:

 Page({
     onLoad: function(){
         ble.onMessageReceived = function(res){

         };
         ble.onDeviceFound = function(device){
             //参数是device 而不要 devices
         };
         ble.autoReconnect = true;
         ble.maxConnections = 1;
         ble.maxRememberedConnections = 5;
         ble.init(function(){ //init 方法里面初始化蓝牙模块
             ble.autoConnectToRememberedDevice(function(res){
                 if(ble.connectedDevices.length == 0){
                     //连接记住的设备失败,跳转到蓝牙发现页面
                 }
             });
         });
     },
     connectToNewDevice: function(newDeviceId){
         //由于设置了最大连接数为1,这时调这个方法就会主动断掉之前的连接
         //这样就不需要先调 disconnect了, 也不需要检查设备ID/状态是否不一致
         ble.connectToDevice(newDeviceId, function(){

         });
     }
 });

既然微信没有实现这个简便的ble,就只有我自己来实现了。

当天下午剩下的一两个小时,已经将这个ble写的差不多了,等到第二天来调试。

DAY #3

早上一来跟领导开会,确认对于Android手机必须开发APP以避免使用蓝牙读写器,小程序仍然要开发,因为iOS系统始终需要读写器才能对芯片进行操作。

开完会就开始进入蓝牙模块的调试。

首先遇到的问题就是获取不到蓝牙设备的名称,尝试从advertiseData解析,尝试在网上找了很多资料,未果。看了论坛的很多帖子,应该是小程序API还不能很好的兼容其他设备的,也就是说对于这块功能还很不成熟。

于是就只有暂时放弃解决蓝牙设备名称的问题。

继续完善ble模块,到下午下班的时候蓝牙连接模块已经比较成熟了,上文提到的那些功能全部都已实现,并且封装之后的BluetoothManager非常好用,表现也很稳定,体验很流畅,跟iOS系统的wifi连接模块的体验差不多。

DAY #4

早上一来就开始继续写蓝牙的读写功能。这里不得不吐槽一下微信小程序的架构师们,开发人员使用你们设计的接口,就完成一个读写功能需要调好几个接口,太复杂了。下方是我写的ble开放的几个接口,真希望微信小程序的架构师们也能开发一些这样的快捷接口:

 Page({
     onLoad: function(){
         ble.onMessageReceived = function(res){
             if(询卡指令返回){
                 ble.sendMessage("发送select指令");
             }
         };
         ble.init(......);
     },
     doInventory: function(){
         ble.sendMessage("发送询卡指令")
     }
 });

等到把这部分代码写完,正好公司购买的蓝牙读写器到货,马上拆开进行测试。

首先是启动微信小程序,自动进入发现页面,很快找到了新的蓝牙设备,值得庆幸的是竟然读到了新设备的名称(而不是像其他蓝牙设备一样返回空)。选择该设备进行连接,非常顺利,然后继续测试了一下断开自动重连,更换设备(保持单一连接),记住连接过的蓝牙设备,中途修复了两三个小bug,又改进了一下体验,应该说连接部分比较完美了。

然后就是要测试第一个指令:询卡。

由于读写器自带一个询卡按钮,通过按一下按钮就发送询卡指令的方式,所以询卡变得很简单。于是对于代码而言,按了询卡按钮之后应该直接进入wx.notifyBLECharacteristicValueChange(OBJECT)。写好相应初始化以及消息监听代码之后,就等着按一下询卡按钮了。

一按,果然收到消息回调了!调试了这么多天,终于可以触发NFC消息通知了!虽然很简单,但是确实是很激动。

消息回调虽然收到了,但是不知道响应消息的格式,也就无法解析,不晓得芯片是不是正确响应了。问了芯片厂家,又问了读写器生产厂家,终于问到了询卡指令的响应格式,按照格式解析出来了UID,这么多天终于读到了UID!再对比芯片厂家提供的demo app以及读写器厂家提提供的桌面程序,确认读到的UID是正确的。

接下来便是发送select指令。下图为15693 select指令格式:

记录使用微信小程序的NFC和蓝牙功能读取15693芯片的开发历程

首先第一个字节flags就不知道怎么拼,select字节传0x25,UID已得到,CRC不知道怎么计算。

在网上看了很多资料,包括芯片厂家也说CRC一般是不需要传值的,因为大多数读写器都会在内部进行CRC的计算。好吧,我暂且相信,那么就只剩下一个参数了,那就是flags参数。按照15693协议规定,试了多种组合,尝试发送select指令(在第一个询卡成功的消息回调中发送),然而收不到任何响应消息。

下班后折腾了一个多小时,做了各种数据格式的尝试,翻阅了多个资料,将手里的15693协议的相关部分阅读了多次,还是收不到任何响应消息。无赖,不得不下班了。等第二天再来研究吧。

DAY #5

继续研究select指令。在跟读写器厂家交流的过程中得知,CRC是必须自己计算的,不能不传,然后就从读写器厂家提供的DEMO代码中找到了CRC的算法,然后用JS实现了一遍,然后好不容易在读写器厂家的技术文档中看到一个CRC计算示例,根据示例的传入参数和计算结果再次确认了自己的JS算法是正确的。

然后,不管怎么传参数格式,select指令发送成功后收不到任何消息。

在绝望之时,干脆不用读写器自带的询卡按钮,自己按照15693协议发送询卡指令。到这时,在跟读写器厂家沟通的过程中得知,他们生产的蓝牙NFC读写器都有自己的数据传输格式,不能完全按照ISO15693协议来写,得按照他们提供的文档来写,当然,到这时候读写器厂家提供的文档也读过奖金十遍了,请求格式响应格式已比较熟悉。所以几分钟就完成了询卡代码。

写完代码之后进入调试,直接就收到了询卡之后的响应,然而在接下来的发送select指令后还是没有收到任何响应。当然,这也本在预料之中。这个时候对读写器厂家的怀疑不断加深,越来越觉得他们卖的读写器无法读到我们的芯片,想要退货。

但我还是没有死心,尝试按照读写器厂家提供的文档,一步一步组装数据。在之后的大概第3次尝试中,竟然收到了select指令的响应!并且按照响应格式解析数据表明芯片响应成功!

太激动了!!!

后来才明白,为什么按读写器自带的询卡按钮之后发送的select指令收不到响应,而自己通过蓝牙发送的询卡指令就收到了响应,我猜测是因为按钮发起的通讯会话跟蓝牙发起的会话完全就是不同的会话导致的,所以点击读写器自带的询卡按钮之后再通过蓝牙发送select指令,应该就相当于直接对着一张芯片发送select指令,当然不会收到任何响应了。

这时,调通了第一个指令,想当然的我觉得后面的指令肯定不是问题了,肯定就会变得非常简单。

然而,更让人绝望的事情发生了。

要说select指令的困惑,差不多花了我4个小时,并且一开始还是非常相信读写器可以通过蓝牙完成select指令的,一直到最后才有点怀疑读写器的问题。

接下来的这个指令是芯片厂家的一个定制指令,不属于ISO15693协议规定的范围之内,读写器厂家提供的文档中自然不会包含整个定制指令。所以只能按照芯片厂家给的文档来发送指令。

很显然,这肯定是行不通的。读写器肯定只能认识读写器厂家给的文档中定义的指令格式,然而这个浅显的道理我大概花了1个小时才明白,在尝试了多种尝试之后冷静下来一思考就明白了。

然后就是找读写器厂家沟通。这个过程也是非常绝望的,因为在跟芯片厂家沟通的过程中得知,必须要读写器支持15693透传协议才可能发送定制指令。然而问了读写器厂商,他们自己也不确认 这款产品的蓝牙通道是否支持透传,他们有另一款产品支持透传,但是不支持蓝牙,这让我一度觉得肯定要把读写器退货了,然后再重新买一个支持15693协议并支持蓝牙透传的读写器。

然后抱着一丝丝希望,跟读写器厂家咨询了很久,他们建议我按照他们另一款支持透传协议的读写器的文档来开发。

真的自己都不抱什么希望了,只是再写几行代码总比立即去买一个新的读写器来的更简单一些,于是又做了一次尝试。

这次按照另一款读写器的透传协议,更改了定制指令的格式。

竟然!竟然直接就收到了芯片响应!

太激动了!

激动了3分钟之后,静下来一看,响应的数据太短,肯定不是正确的数据,然后比对文档中的响应格式,确认芯片没有能够正确响应。

这时还是继续怀疑读写器不支持透传指令,于是再跟读写器厂家沟通,把响应数据发给了他们看,他们确定芯片是收到了指令了,只是指令格式不对,芯片没有回发正确的数据。

这时已经下班十几分钟了,感觉就还剩这一个问题了,如果不能确定该读写器是否支持透传,那接下来的周末肯定是无法好好休息的。

但这时已经对读写器很有信心了,比较坚定的认为是定制指令格式不对。然后又找芯片厂家要了他们以前发送的demo代码,指令的示例,然后又把芯片厂家的对应文档阅读了几遍,几乎一个字一个字去理解协议中的每一个字。

终于,终于发现指令中的某个代表响应数据长度的字节计算错误,当把这个错误数据修复之后就立即收到了正确的响应了。

太不容易了!

记录使用微信小程序的NFC和蓝牙功能读取15693芯片的开发历程

终于可以安心的下班了!

后记

之后的开发都比较顺利了,因为要么就是给读写器发送读写器自定义的指令,要么就是用透传功能发送芯片自定义的指令,可以说是所有指令都可以发送了。

这次芯片项目开发收货到最有价值的经验就是,一般读写器厂家都会有自己定义的一套指令格式,或者SDK,当通过读写器操作芯片的时候,必须要首先按照读写器的API文档来写,否则芯片不可能有响应的。

信息