微信小程序蓝牙打印指北

2,907 阅读8分钟

上半年的时候客户要求实现小程序蓝牙打印。网上一找,文章不多,杂且不全。于是萌生了写一篇小程序蓝牙打印的文章,不能说很详细,只能说聊胜于无吧。

如果没心思看长篇大论,直接拖到底下拿走demo

蓝牙打印

小程序或者说移动端实现打印目前普遍就两种方式,一个是云打印,另一种就是蓝牙打印
云打印的话就是将要打印的数据发送给远端的连着打印机的服务器,服务器通过USB或者其他途径将打印数据发送给打印机,完成打印。这种方式普通的打印机都能完成。
另一种的话就是蓝牙打印,更确切的说是一种通过特殊通信协议,让移动端跟打印机直接无线连接,通过程序传输数据给打印机完成打印。蓝牙打印就属于这种范畴。

蓝牙简介

蓝牙相信大家都很熟悉了。作为一种无线短距离通信技术,目前已经广泛的得到应用,并已成为接入物联网(IOT)的主要技术。
实际上蓝牙技术的核心有不同版本。目前最常见的是蓝牙BR/EDR(即基本速率/增强数据率)和低功耗蓝牙(Bluetooth Low Energy)技术,蓝牙BR/EDR主要应用在蓝牙2.0/2.1版,一般用于扬声器和耳机等产品;而低功耗蓝牙技术主要应用在蓝牙4.0/4.1/4.2版,主要用于市面上的最新产品中,例如手环、智能家居设备、汽车电子、医疗设备、Beacon感应器(通过蓝牙技术发送数据的小型发射器)等。我们用于蓝牙打印的就属于低功耗蓝牙

  • 蓝牙技术优点:
  1. 无处不在,应用广泛;
  2. 应用成本低;
  3. 低延时;
  4. 抗干扰能力强
  • 蓝牙技术缺点:
  1. 功耗问题。传输过程中虽然耗能少,但是为了及时响应连接请求,在等待过程中的轮询访问却是十分耗能的。这一点后面会提到;
  2. 连接过程繁琐。作为使用者可能毫无感觉,但是作为开发者就明白从搜索蓝牙设备,到配对连接,到最后传输整个工程是多么繁琐。这一点后面也会提到;
  3. 安全性问题。

低功耗蓝牙连接过程

打个比方

这里我们打个比方。每一台蓝牙设备(Device)就是一个公司。一台蓝牙设备下有很多服务(Service),就好比一个公司下有很多部门。而这些服务下面又有很多特征(Characteristic),每一个特征有不同的属性(Property),这就好比每个部门下有不同的员工,每个员工能力不同,负责的事情也不同。 移动端连接蓝牙打印机并打印的过程,就好比去一家特定的公司(Device),特定的部门(Service),找到特定的员工(Characteristic)。这个员工具有提供打印的能力(Property)。具体看下图:

企业微信截图_20210913102918.png

微信小程序方法

具体到微信调用方法如下:

graph TD
1([断开低功耗蓝牙连接]) --> 初始化蓝牙模块 --> 搜索蓝牙 --> 选中蓝牙设备 --> 2([停止搜索蓝牙]) --> 连接低功耗蓝牙设备 --> 传输数据 --> 断开蓝牙连接 --> 关闭蓝牙模块

1. wx.closeBLEConnection(断开低功耗蓝牙连接)
如果手机之前连过蓝牙设备,首先需要断开。

2. wx.openBluetoothAdapter(初始化蓝牙模块)

3. wx.startBluetoothDevicesDiscovery(搜索蓝牙)
来自官方文档的提醒:此操作比较耗费系统资源,请在搜索到需要的设备后及时调用 wx.stopBluetoothDevicesDiscovery 停止搜索。

4. wx.getBluetoothDevices(获取搜索到的蓝牙设备: devices)
获取在蓝牙模块生效期间所有搜索到的蓝牙设备。包括已经和本机处于连接状态的设备。返回一个设备列表devices。

5. 选中需要连接的设备(deviceId)
获取需要连接设备的deviceId。

6. wx.stopBluetoothDevicesDiscovery(停止搜索蓝牙)
关闭搜索,释放系统资源。

7. wx.createBLEConnection(连接低功耗蓝牙设备)
连接蓝牙低功耗设备。用第5步中拿到的deviceId去建立低功耗蓝牙连接。

8. wx.getBLEDeviceServices(获取蓝牙设备所有服务: (deviceId) => services)
建立蓝牙连接之后,需要根据deviceId获取该蓝牙设备下所有的服务services。遍历services,找到isPrimary为真值的service(蓝牙设备的主服务),得到该service的serviceId。

9. wx.getBLEDeviceCharacteristics(获取蓝牙设备某个服务中所有特征值:(deviceId, serviceId) => characteristicId)
获取蓝牙低功耗设备(deviceId)某个服务(serviceId)中所有特征 (characteristics)。遍历characteristics,找到properties.write为真值的characteristicId。因为设备的特征支持 write 才可以写入数据。

10. wx.writeBLECharacteristicValue(打印)
现在我们已经拿到了蓝牙设备的deviceId,serviceId,characteristicId,接下来只要把我们要打印的内容转换成蓝牙设备能识别的二进制数据,并向低功耗蓝牙设备特征值中写入即可。 由于用的是低功耗蓝牙,写入的数据需要进行分包发送,具体实现见demo。
内容转换根据打印设备支持的指令集不同而不同。 指令集一般包括打印机设置和操作,打印内容配置等。一般打印设备出厂都有专门指令集文档支持,类似于浏览器,不同厂商对于标准的执行不一而足,需要认真阅读文档。

11.离开页面时取消蓝牙连接 wx.closeBLEConnection

12.关闭蓝牙模块 wx.closeBluetoothAdapter

关于这块通信过程微信官方也有比较详细的说明文档

打印

其实就是往已建立连接的打印机写数据的过程。

指令集

这里就不得不提到打印指令。打印指令,又称打印控制命令。打印机接收打印指令完成相应的打印操作。无论是对打印机的设置、操作还是打印内容的设置、格式设置和排版,都通过这一系列打印指令来控制。 打印机所能识别的指令集合就成为指令集。不同的打印机厂商实现了不同的指令集,就像不同的浏览器有不同的内核一样。目前主流的指令集有如下几种:

  • Epson公司的ESC命令集(普通打印机)
  • HP公司的PCL命令集
  • Adobe公司的PostScript(简称PS)命令集 其他的还有CPCL命令集(移动打印机), TSPL命令集(标签打印机)。

其中ESC命令集是针式打印机和票据打印领域的事实上的工业标准。而ESC/POS打印命令集是ESC打印控制命令的简化版本,现在大多数票据打印都采用ESC/POS指令集。本demo基于ESC/POS指令集对一些常用的打印功能做了封装。详细的ESC/POS指令集可以看这里

代码实现

demo里我实现了一个PrinterJobs的构造函数(参考了另一个demo,在此基础上做了扩展)。通过该构造函数创建的实例,用来把打印指令生成类型化数组数据,最后转成ArrayBuffer发给打印机。

举个例子:打印文字“开始打印了”,居中,中号字体,带下划线。

微信图片_20210915110534.jpg 代码如下:

let printerJobs = new PrinterJobs();
printerJobs
  .setAlign("ct") // 居中
  .setFontSize(2, 2) // 设置字号
  .setUnderline(true) // 设置下划线
  .println("开始打印了") // 设置内容并打印
let buffer = printerJobs.buffer(); // 要写入打印机的ArrayBuffer
doPrint(buffer) // 写入数据

我们打印一下类型数组数据,如下
[27, 64, 29, 80, 203, 203, 27, 97, 1, 29, 33, 17, 27, 45, 1, 191, 170, 202, 188, 180, 242, 211, 161, 193, 203, 10]

我们根据代码来拆解一下这个类型数组。

代码对应类型数组
初始化部分let printerJobs = new PrinterJobs()[27, 64, 29, 80, 203, 203]
居中printerJobs.setAlign("ct")[27, 97, 1]
设置字号printerJobs.setFontSize(2, 2)[29, 33, 17]
设置下划线printerJobs.setUnderline(true)[27, 45, 1]
打印内容printerJobs.println("开始打印了")文字部分:[191, 170, 202, 188, 180, 242, 211, 161, 193, 203]
打印指令:[10]

数组里的数字是怎么确定的,这就要去查看指令集了。一般打印机都会附赠指令集文档。比如居中的指令如下图:

企业微信截图_20210914133144.png 图片截自:reference.epson-biz.com/modules/ref…

这里27,97是文字对齐方式的起始常量,n是变量,n = 1 表示居中。最终结果就是[27, 97, 1]
而文字内容就要通过编码转化,尤其是汉字,参考这里text-encoding

写数据的时候需要注意的是,对于低功耗蓝牙设备,传输数据大小有限制,超过限制会导致传输失败。因此需要对数据分块延时传输。下面是微信官方的解释:

在与蓝牙设备传输数据时,需要注意 MTU(最大传输单元)。如果数据量超过 MTU 会导致错误,建议根据蓝牙设备协议进行分片传输。安卓设备可以调用 wx.setBLEMTU 进行 MTU 协商。在 MTU 未知的情况下,建议使用 20 字节为单位传输。

Demo

请看这里:github.com/akizmh/blue…

参考