某人

此前素未谋面、此后遥遥无期

0%

rn蓝牙打印

蓝牙4.0

蓝牙4.0标准包含两个蓝牙标准,准确的说,是一个双模的标准

现在移动设备上使用的蓝牙大多是4.0,而蓝牙 4.0 有两个分支,经典 4.0BLE4.0

经典蓝牙(classic Bluetooth)

经典蓝牙可以用与数据量比较大的传输,如语音,音乐,较高数据量传输

建立连接

经典蓝牙建立连接的方式实际上就是Socket的连接的建立。只不过这里不是直接用Socket,而是BluetoothSocket

获取BluetoothSocket的方式也很简单,利用搜索找到的BluetoothDevice,调用其方法createRfcommSocketToServiceRecord(UUID)

最后,使用获取到的BluetoothDevice调用其方法connect()就建立了经典蓝牙设备之间的连接通道。

数据通信

当建立连接后,就可以直接使用BluetoothSocketgetOutputStream()方法获取输出流写入需要发送的数据。读取发送回来的数据,则是调用BluetoothSocketgetInputStream()方法获取输入流读取

低功耗蓝牙(Bluetooth low energy,简称BLE或者LE)

低功耗蓝牙应用于实时性要求比较高,但是数据速率比较低的产品,如遥控类的,如鼠标,键盘,遥控鼠标(Air Mouse),传感设备的数据发送,如心跳带,血压计,温度传感器等

BLE的优点是快速搜索,快速连接,超低功耗保持连接和传输数据,弱点是数据传输速率低

建立连接

硬件条件是,蓝牙得至少是低功耗蓝牙版本,然后安卓系统的话,至少得是Android 4.3以上系统才行

数据通信

BLE分为三部分Service(服务)、Characteristic(特征)、Descriptor(描述符),这三部分都由UUID作为唯一标示符。

一个蓝牙4.0的终端可以包含多个Service;

一个Service可以包含多个Characteristic;

一个Characteristic包含一个Value和多个Descriptor;

一个Descriptor包含一个Value 一般来说;

Characteristic是手机与BLE终端交换数据的关键,Characteristic有跟权限相关的字段,如Property,Property有读写等各种属性,如Notify、Read、Write、WriteWithoutResponse

调用BluetoothGattCharacteristic的方法setValue(value)进行,发送的命令值,其中value一般为byte[];

使用BluetoothGatt的写入方法writeCharacteristic(char)完成命令发送

react-native-ble-plx

一个支持低功耗的蓝牙类库

Android (example setup):

1
2
3
npm install --save react-native-ble-plx
react-native link react-native-ble-plx
#安装依赖

在app模块的build.gradle中,确保min SDK版本至少为18:

1
2
3
4
5
6
7
android {
...
defaultConfig {
minSdkVersion 18
...
}
}

添加权限AndroidManifest.xml

注意: 添加GPS和网络定位权限

1
2
3
4
5
6
7
8
<!-- 允许程序连接到已配对的蓝牙设备-->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<!-- 允许程序发现和配对蓝牙设备-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- GPS权限-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 网络定位-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

热敏打印机指令集

以下命令是部分命令,具体参考文百度文库热敏打印机指令集文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const blePlxCommands = {
asciiFont:[0x1b, 0x4d, 0x00],//标准ASCII字体
compressAsciiFont:[0x1b, 0x4d, 0x01],// 压缩ASCII字体
fontWeight:[0x1b, 0x45, 0x01],//选择加粗模式
cancelFontWeight:[0x1b, 0x45, 0x00],//取消加粗模式
alignLeft:[0x1b, 0x61, 0x30],//左对齐
alignRight:[0x1b, 0x61, 0x32],//右对齐
alignCenter:[0x1b, 0x61, 0x31],//居中对齐
cutPaper:[0x1b, 0x69],//全切纸
cutHalfPaper:[0x1b, 0x6d],//半切纸
nextOneLinePaper:[0x0a],//走一行纸
clockwiseDegree90:[0x1b, 0x56, 0x01], //选择顺时针旋转90°
reverseClockwise90:[0x1b, 0x56, 0x00],//取消顺时针旋转90°
imgBefore:[0x1B, 0x33, 0x00],// 发送打印图片设置图片行距为0
imgAfter:[0x1d, 0x4c, 0x1f, 0x1f],//发送结束指令(选择左边空白)
initClear:[0x1b,0x40],//初始化打印机,复位打印机,清除打印缓冲区
setZhFontMode:[0x1c,0x26]//设置中文字符模式
}

react-native-ble-plx简单使用

BlueLoupe.apkAPP可以检测设备是否支持低功耗蓝牙

蓝牙服务于特征的获取并与数据的通信

react-native

bleplx.js(简要代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
import {Platform,Alert,NativeModules} from 'react-native'
import { Buffer } from 'buffer';
import { BleManager } from 'react-native-ble-plx';
import XToast from '@app/widget/x-toast/';
import lodash from "lodash"
import iconv from "iconv-lite"

const BlePlxPrint = NativeModules.BlePlxPrint;
/**
* [bluetoothCommands 蓝牙打印命令]
* @type {Object}
*/
const blePlxCommands = {
asciiFont:[0x1b, 0x4d, 0x00],//标准ASCII字体
compressAsciiFont:[0x1b, 0x4d, 0x01],// 压缩ASCII字体
fontWeight:[0x1b, 0x45, 0x01],//选择加粗模式
cancelFontWeight:[0x1b, 0x45, 0x00],//取消加粗模式
alignLeft:[0x1b, 0x61, 0x30],//左对齐
alignRight:[0x1b, 0x61, 0x32],//右对齐
alignCenter:[0x1b, 0x61, 0x31],//居中对齐
cutPaper:[0x1b, 0x69],//全切纸
cutHalfPaper:[0x1b, 0x6d],//半切纸
nextOneLinePaper:[0x0a],//走一行纸
clockwiseDegree90:[0x1b, 0x56, 0x01], //选择顺时针旋转90°
reverseClockwise90:[0x1b, 0x56, 0x00],//取消顺时针旋转90°
imgBefore:[0x1B, 0x33, 0x00],// 发送打印图片设置图片行距为0
imgAfter:[0x1d, 0x4c, 0x1f, 0x1f],//发送结束指令(选择左边空白)
initClear:[0x1b,0x40],//初始化打印机,复位打印机,清除打印缓冲区
setZhFontMode:[0x1c,0x26]//设置中文字符模式
}

/**
* [_cacheConnectDeviceMap 缓存已连接的设备]
* @type {Map}
*/

//缓存所有连接过的设备
let _cacheConnectDeviceMap = new Map();
//缓存设备的UUID
let _cacheConnectDeviceUUIDMap = new Map();

/*************************************ConnectDevice**************************************************/
/**
* 已连接成功的设备
* [updateCacheConnectDevice 新增并更新缓存设备]
* @param {[type]} connectDevice [description]
* @return {[type]} [description]
*/
function updateCacheConnectDevice(connectDevice){
if(!connectDevice || !connectDevice.id){
XToast.show("连接设备[id]不能为空")
return;
}
_cacheConnectDeviceMap.set(connectDevice.id,connectDevice); //使用Map类型保存搜索到的蓝牙设备,确保列表不显示重复的设备
}

/**
* [getCacheConnectDeviceById 获取指定连接设备]
* @param {[type]} id [description]
* @return {[type]} [description]
*/
function getCacheConnectDeviceById(id){
if(!id){
XToast.show("获取设备[id]不能为空")
return;
}
if(_cacheConnectDeviceMap.has(id)){
return _cacheConnectDeviceMap.get(id);
} else {
return null;
}
}

/**
* 已连接成功的设备
* [removeCacheConnectDevice 移除缓存设备]
* @param {[type]} connectDevice [description]
* @return {[Object]} [description]
*/
function removeCacheConnectDevice(connectDevice){
if(!connectDevice || !connectDevice.id){
XToast.show("连接设备[id]不能为空")
return;
}
if(_cacheConnectDeviceMap.has(connectDevice.id)){
_cacheConnectDeviceMap.delete(connectDevice.id);
}
}

/***************************************UUID**************************************************/
/**
* [removeCacheConnectDeviceUUIDMap 移除缓存设备]
* @param {[type]} connectDevice [description]
* @return {[type]} [description]
*/
function removeCacheConnectDeviceUUID(id){
if(!id){
XToast.show("连接设备[id]不能为空")
return;
}
if(_cacheConnectDeviceUUIDMap.has(id)){
_cacheConnectDeviceUUIDMap.delete(id);
}
}

/**
* [getCacheConnectDeviceUUIDById 返回设备UUID]
* @param {[type]} id [description]
* @return {[Object]} [description]
*/
function getCacheConnectDeviceUUIDById(id){
if(!id){
XToast.show("获取设备[id]不能为空")
return;
}
if(_cacheConnectDeviceUUIDMap.has(id)){
return _cacheConnectDeviceUUIDMap.get(id);
} else {
return null;
}
}

/**
* [updateCacheConnectDeviceUUID 更新设备的UUID]
* @param {[type]} id [description]
* @param {[type]} data [description]
* @return {[type]} [description]
*/
function updateCacheConnectDeviceUUID(id,data){
if(!id || !data){
XToast.show("连接设备[id]与 [data]不能为空")
return;
}
_cacheConnectDeviceUUIDMap.set(id,data); //使用Map类型保存搜索到的蓝牙设备,的UUID
}

/**
* [fetchServicesAndCharacteristicsForDevice 获取设备]
* @param {[type]} device [description]
* @return {Promise} [description]
*/
async function fetchServicesAndCharacteristicsForDevice(id){
if(!id){
XToast.show("[id]不存在无法获取服务")
return;
}
let device = getCacheConnectDeviceById(id);
if(!device){
XToast.show("连接设备不存在无法获取服务")
return;
}
let servicesMap = {};
let characteristicsMap = {};
//获取所有服务
let services = await device.services();
for (let service of services) {
characteristicsMap = {}
//获取该服务的所有特征
let characteristics = await service.characteristics();

for (let characteristic of characteristics) {
characteristicsMap[characteristic.uuid] = {
uuid: characteristic.uuid,
isReadable: characteristic.isReadable,
isWritableWithResponse: characteristic.isWritableWithResponse,
isWritableWithoutResponse: characteristic.isWritableWithoutResponse,
isNotifiable: characteristic.isNotifiable,
isNotifying: characteristic.isNotifying,
value: characteristic.value
}
}
servicesMap[service.uuid] = {
uuid: service.uuid,
isPrimary: service.isPrimary,
characteristicsCount: characteristics.length,//特征数量
characteristics: characteristicsMap
}
}
return servicesMap;
}

/**
* [getBlePlxUUID 获取蓝牙UUID]
* @return {[type]} [description]
*/
async function getBlePlxUUID(id){
let services = await fetchServicesAndCharacteristicsForDevice(id);
if(!services){
XToast.show("服务不存在,无法获取[UUID]");
return;
}
let result = {
readServiceUUID:[],
readCharacteristicUUID:[],

writeWithResponseServiceUUID:[],
writeWithResponseCharacteristicUUID:[],

writeWithoutResponseServiceUUID:[],
writeWithoutResponseCharacteristicUUID:[],

nofityServiceUUID:[],
nofityCharacteristicUUID:[],
}

for(let i in services){
let charchteristic = services[i].characteristics;

for(let j in charchteristic){
//读
if(charchteristic[j].isReadable){
result.readServiceUUID.push(services[i].uuid);
result.readCharacteristicUUID.push(charchteristic[j].uuid);
}
//写并响应
if(charchteristic[j].isWritableWithResponse){
result.writeWithResponseServiceUUID.push(services[i].uuid);
result.writeWithResponseCharacteristicUUID.push(charchteristic[j].uuid);
}
//写无响应
if(charchteristic[j].isWritableWithoutResponse){
result.writeWithoutResponseServiceUUID.push(services[i].uuid);
result.writeWithoutResponseCharacteristicUUID.push(charchteristic[j].uuid);
}
//通知
if(charchteristic[j].isNotifiable){
result.nofityServiceUUID.push(services[i].uuid);
result.nofityCharacteristicUUID.push(charchteristic[j].uuid);
}
}
}

// console.info("@getBlePlxUUID",result);
return result;
}

/**
* [initBleplx 初始化蓝牙]
* @return {[type]} [description]
*/
function initBleplx(){
//确保全局只有一个BleManager实例,BleModule类保存着蓝牙的连接信息
if(!global.BLEPlxMag){
global.BLEPlxMag = new BleManager();
}
}

/**
* [blePlxWrite 写数据]
* @return {[type]} [description]
*/
async function blePlxWrite(id,value){
if(!id){
XToast.show("[id]不能为空");
return;
}
let uuidObj = getCacheConnectDeviceUUIDById(id);
if(!uuidObj){
XToast.show("获取[UUID service Obejct]失败,请重新连接");
return;
}
if(!uuidObj.writeWithResponseServiceUUID[0]){
XToast.show("获取[UUID service]失败");
return
}
if(!uuidObj.writeWithResponseCharacteristicUUID[0]){
XToast.show("获取[UUID characteristic]失败");
return
}

let deviceId = id;
let serviceUUID = uuidObj.writeWithResponseServiceUUID[0];
let characteristicUUID = uuidObj.writeWithResponseCharacteristicUUID[0];
let base64Value = value;
let transactionId = 'write';
// console.log("@deviceId,serviceUUID,characteristicUUID,base64Value",deviceId,serviceUUID,characteristicUUID,base64Value,"参数结束");
let pse = BLEPlxMag.writeCharacteristicWithResponseForDevice(deviceId,serviceUUID,characteristicUUID,base64Value)
return pse;
}

/**
* [getDeviceIdByTpltypeid 根据模板Id获取指定的打印设备Id]
* @param {[type]} deviceList [description]
* @param {[type]} tpltypeid [description]
* @return {[type]} [description]
*/
function getDeviceIdByTpltypeid(deviceList,tpltypeid){
if(!deviceList || !deviceList.length){
XToast.show("设备列表不存在");
return;
}
let findDevice = null;
for(let device of deviceList){
if(device.tpltypeid == tpltypeid){
findDevice = device;
break;
}
}
if(!findDevice){
XToast.show("未找到设置的打印设备无法打印");
return;
}
return findDevice.id;
}

/**
* [bleplxPrintTaskDeal 打印任务处理]
* @param {[type]} printArr [description]
* @return {[type]} [description]
*/
function bleplxPrintTaskDeal(id,printArr){
let writePromises = []
let toWrite;
let base64Value;
let packetSize = 512;//分包 此数值根据具体设备进行测试
let packetCount;
let packet;
let byteArr;

if(lodash.isArray(printArr)){
printArr.forEach( (item) =>{
//执行命令
if(item.type == 1){
toWrite = Buffer.from(item.values);
base64Value = toWrite.toString('base64');
writePromises.push(blePlxWrite(id,base64Value));
//执行字符串
} else if(item.type==2){
toWrite = iconv.encode(item.values, 'GBK');
packetCount = Math.ceil(toWrite.length / packetSize);
for (var i = 0; i < packetCount; i++) {
packet = new Buffer(packetSize)
toWrite.copy(packet, 0, i * packetSize, (i + 1) * packetSize)
base64Value = packet.toString('base64');
writePromises.push(blePlxWrite(id,base64Value))
}
//生成二维码
} else if(item.type==3){
toWrite = Buffer.from(item.byte);
base64Value = toWrite.toString('base64');
packetCount = Math.ceil(toWrite.length / packetSize);
for (var i = 0; i < packetCount; i++) {
packet = new Buffer(packetSize)
toWrite.copy(packet, 0, i * packetSize, (i + 1) * packetSize)
base64Value = packet.toString('base64');
writePromises.push(blePlxWrite(id,base64Value))
}
//打印图片
} else if(item.type==4){
toWrite = Buffer.from(item.byte);
packetCount = Math.ceil(toWrite.length / packetSize);
for (var i = 0; i < packetCount; i++) {
packet = new Buffer(packetSize)
toWrite.copy(packet, 0, i * packetSize, (i + 1) * packetSize)
base64Value = packet.toString('base64');
writePromises.push(blePlxWrite(id,base64Value))
}
} else if(item.type == 5){
toWrite = Buffer.from(item.byte);
packetCount = Math.ceil(toWrite.length / packetSize);
for (var i = 0; i < packetCount; i++) {
packet = new Buffer(packetSize)
toWrite.copy(packet, 0, i * packetSize, (i + 1) * packetSize)
base64Value = packet.toString('base64');
writePromises.push(blePlxWrite(id,base64Value))
}
} else if(item.type == 6 && item.byte && item.byte.length){
toWrite = Buffer.from(item.byte);
packetCount = Math.ceil(toWrite.length / packetSize);
for (var i = 0; i < packetCount; i++) {
packet = new Buffer(packetSize)
toWrite.copy(packet, 0, i * packetSize, (i + 1) * packetSize)
base64Value = packet.toString('base64');
writePromises.push(blePlxWrite(id,base64Value))
}
}
})
} else if(lodash.isString(printArr)){
toWrite = iconv.encode(item.values, 'GBK');
packetCount = Math.ceil(toWrite.length / packetSize);
for (var i = 0; i < packetCount; i++) {
packet = new Buffer(packetSize)
toWrite.copy(packet, 0, i * packetSize, (i + 1) * packetSize)
base64Value = packet.toString('base64');
writePromises.push(blePlxWrite(id,base64Value))
}
}
// console.log("writePromises",writePromises)
return writePromises;
}

/**
* [ConvertPrintImage description]
* @return {Promise} [description]
*/
async function convertPrintImage(rows){
if(!rows || !rows.length){
XToast.show("数据不能为空");
return;
}
let data = await BlePlxPrint.getSingleConvertData(rows);
return data;
}

export {
initBleplx,
updateCacheConnectDevice,
removeCacheConnectDevice,
getCacheConnectDeviceById,

getCacheConnectDeviceUUIDById,
removeCacheConnectDeviceUUID,
updateCacheConnectDeviceUUID,

fetchServicesAndCharacteristicsForDevice,
getBlePlxUUID,
blePlxWrite,
bleplxPrintTaskDeal,
blePlxCommands,
convertPrintImage,
}

blecp.js(简要代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import React, { Component } from "react";
import { Buffer } from 'buffer';
import moment from "moment";
import {
initBleplx,
updateCacheConnectDevice,
removeCacheConnectDevice,
getCacheConnectDeviceById,
getCacheConnectDeviceUUIDById,
removeCacheConnectDeviceUUID,
updateCacheConnectDeviceUUID,

fetchServicesAndCharacteristicsForDevice,
getBlePlxUUID,
bleplxPrintTaskDeal,
blePlxWrite,
blePlxCommands,

convertPrintImage,
} from './bleplx.js';

export default class Blecp extends Component {
constructor(props){
super(props);
this.state = {
scaning:false, //是否扫描附近蓝牙
isConnecting:false,//是否连接设备中
writing:false, //正在写数据
}
this.deviceMap = new Map();
initBleplx();
}

componentWillMount(){
// 监听蓝牙开关
BLEPlxMag.onStateChange((state) => {
if(state == 'PoweredOn'){
this.scan();
}
})
}
componentDidMount() {}
componentWillUnmount() {}

/**
* 扫描设备 记得检查权限
*/
scan = () => {
if(!this.state.scaning) {
this.setState({scaning:true});
this.deviceMap.clear();
BLEPlxMag.startDeviceScan(null, null, (error, device) => {
if (error) {
if(error.errorCode == 102){
XToast.show("请打开手机蓝牙后再搜索");
}
this.setState({scaning:false});
}else{
// console.log(device.id,device.name);
this.deviceMap.set(device.id,device); //使用Map类型保存搜索到的蓝牙设备,确保列表不显示重复的设备
let arr = [...this.deviceMap.values()];
}
})
this.scanTimer && clearTimeout(this.scanTimer);
this.scanTimer = setTimeout(()=>{
if(this.state.scaning){
BLEPlxMag.stopDeviceScan();
this.setState({scaning:false});
}
},6000) //6秒后停止搜索
} else {
BLEPlxMag.stopDeviceScan();
this.setState({scaning:false});
}
}

//开始连接设备
connect = (item,index) => {
if(this.state.scaning){ //连接的时候正在扫描,先停止扫描
BLEPlxMag.stopDeviceScan();
this.setState({scaning:false});
}
if(this.state.isConnecting){
XToast.show("正在连接中...");
// console.log('当前蓝牙正在连接时不能打开另一个连接进程');
return
}
if(item.isConnecting){
XToast.show("该蓝牙正在连接中...");
return;
}

this.setState({
isConnecting:true
})
BLEPlxMag.connectToDevice(item.id)
.then(device=>{
//发现所需要的服务和特点是只执行一次。 另外 它可以是一个漫长的过程取决于数量的特点和可用的服务。
return device.discoverAllServicesAndCharacteristics();
})
.then( async(device) => {
updateCacheConnectDevice(device);
let objUUID = await getBlePlxUUID(item.id);
if(objUUID && device.id){
updateCacheConnectDevice(device);
updateCacheConnectDeviceUUID(device.id,objUUID);
this.setState({
isConnecting:false
})
item.isConnected = true;
this.onDisconnect(item);
XToast.show("设备连接成功");
} else {
this.setState({
isConnecting:false
});
item.isConnected = false;
XToast.show("设备连接失败");
}
})
.catch(err=>{
this.setState({isConnecting:false})
XToast.show("设备连接失败");
})
}

//监听蓝牙断开
onDisconnect = (item) => {
const {btconnectStore} = this.props;
BLEPlxMag.onDeviceDisconnected(item.id,(error,device)=>{
if(error){
XToast.show("蓝牙遇到错误自动断开");
//蓝牙遇到错误自动断开
} else {
XToast.show("蓝牙已断开");
}
if(device){
removeCacheConnectDevice(device);
removeCacheConnectDeviceUUID(item.id)
}
})
}

//断开蓝牙设备
_disconnect = (item,index) => {
BLEPlxMag.cancelDeviceConnection(item.id).then(device=>{
// console.log('disconnect success',device);
removeCacheConnectDevice(device);
removeCacheConnectDeviceUUID(item.id);
XToast.show("断开连接设备成功");
}).catch(err=>{
XToast.show("断开连接异常");
})
}

//写入数据
_write = (item,index) => {
if(this.state.writing){
XToast.show("正在写数据中,请稍后再操作...");
return;
}
this.setState({
writing:true
},() => {
let printArr = [
{
type:1,
values:blePlxCommands.initClear
},
{
type:2,
values:`静女其姝,俟我于城隅。爱而不见,搔首踟蹰。静女其娈,贻我彤管。彤管有炜,说怿女美。自牧归荑,洵美且异...`
},
//打印二维码
{
type:1,
values:blePlxCommands.imgBefore
},
{
type:3,
values:{
content:"我是二维码",
size:280,
logo:""//可选
}
},
{
type:1,
values:blePlxCommands.initClear
},
{
type:1,
values:blePlxCommands.imgBefore
},
{
type:4,
values:{
base64Str:"data:image/png;base64,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"
}
},
{
type:1,
values:blePlxCommands.nextOneLinePaper
},
{
type:1,
values:blePlxCommands.cutHalfPaper
}
];
convertPrintImage(printArr).then((rows) => {
const writePromises = bleplxPrintTaskDeal(item.id,rows);
Promise.all(writePromises).then((result) => {
this.setState({writing:false})
XToast.show("打印完成")
}).catch((err) => {
this.setState({writing:false})
XToast.show("打印任务失败")
})
});
})
}

render(){
return null;
}
}

Android部分代码

对打印图片的数据进行处理

Android原生代码BlePlxPrint.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package com.xx.yy.print;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Base64;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.xx.yy.utils.BaseUtil;
import com.xx.yy.utils.PictureUtils;
import com.xx.yy.utils.QRCodeUtil;

/**
* Created by Administrator on 2018/11/19.
*/

public class BlePlxPrint extends ReactContextBaseJavaModule {

private ReactContext reactContext;

public BlePlxPrint(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}

/**
* return string 这个名字在JavaScript端标记这个模块
* 这样就可以在JavaScript中通过React.NativeModules.ToastForAndroid访问到这个模块
*
* @return
*/
@Override
public String getName() {
return "BlePlxPrint";
}

@Override
public boolean canOverrideExistingModule() {
return true;
}


/**
* 返回打印图片的字节数组
* @param type=3=qrcode type=4=img type=5=barcode
* @param map
*/
private WritableArray ConvertPrintToImage(int type ,ReadableMap map){
byte[] imgByte = {};
WritableArray jsArr = Arguments.createArray();
Bitmap bitmap = null;
try {
switch (type){
case 3:
String qrCodeContent = map.getString("content");
int qrCodeSize = map.getInt("size");
if( map.hasKey("logo") && !TextUtils.isEmpty(map.getString("logo"))){
String logo = map.getString("logo");
Bitmap bitmapLogo = PictureUtils.base64StringToBitmap(logo);
bitmap = QRCodeUtil.createQRCodeBitmap(qrCodeContent,qrCodeSize,bitmapLogo,0.2f);
} else {
bitmap = QRCodeUtil.createQRCodeBitmap(qrCodeContent,qrCodeSize);
}
break;
case 4:
String base64Str = map.getString("base64Str");
bitmap = PictureUtils.base64StringToBitmap(base64Str);
break;
case 5:
String barCodeContent = map.getString("content");
int barCodeWidth = map.getInt("width");
int barCodeHeight = map.getInt("height");
bitmap = QRCodeUtil.createBarCodeBitmap(barCodeContent,barCodeWidth,barCodeHeight);
break;
case 6:
String base64Value = map.getString("base64Str");
imgByte = Base64.decode(base64Value, Base64.DEFAULT);
default:
break;
}
if(bitmap != null){
//拼接字节数组
byte[] draw2PxPoint = PictureUtils.draw2PxPoint(bitmap);
imgByte = BaseUtil.byteMergerAll(draw2PxPoint);
for (int i =0;i<imgByte.length;i++){
jsArr.pushInt(imgByte[i]);
}
}
if(type == 6){
for (int i =0;i<imgByte.length;i++){
jsArr.pushInt(imgByte[i]);
}
}
} catch (Exception e){
e.printStackTrace();
}
return jsArr;
}


/**
* 转换打印数据处理其中的图片
* @param rows
* @return
*/
private WritableArray getConvertData(ReadableArray rows){
WritableArray writeArray = Arguments.createArray();
for (int i=0;i<rows.size() ;i++){

ReadableMap readMap = rows.getMap(i);
Bundle bundle = Arguments.toBundle(readMap);
WritableMap writeMap = Arguments.fromBundle(bundle);
WritableArray jsArr = Arguments.createArray();

int type = readMap.getInt("type");
ReadableType valuesType = readMap.getType("values");

switch (valuesType) {
case Null:
case Boolean:
case String:
case Array:
break;
case Map:
ReadableMap values = readMap.getMap("values");
jsArr = ConvertPrintToImage(type,values);
break;
default:
throw new IllegalArgumentException("Could not convert object in Map.");
}
writeMap.putArray("byte",jsArr);
writeArray.pushMap(writeMap);
}
return writeArray;
}


/**
* 返回打印图片的字节数组
* @param type=3=qrcode type=4=img type=barcode
* @param map
* @param promise
*/
@ReactMethod
public void getPrintImageByte(int type, ReadableMap map, Promise promise){
WritableArray jsArr = ConvertPrintToImage(type,map);
promise.resolve(jsArr);
}

/**
* 转换数据到新字段,并加入新字段
* @param rows
* @param promise
*/
@ReactMethod
public void getSingleConvertData(ReadableArray rows,Promise promise){
WritableArray writeArray = null;
if(rows != null){
writeArray = getConvertData(rows);
}
promise.resolve(writeArray);
}
}

图片工具:

PictureUtils.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package com.xx.yy.utils;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import android.util.Log;

import java.io.ByteArrayOutputStream;

/**
* Created by Administrator on 2018/11/7.
*/

public class PictureUtils {
/**
* 把一张Bitmap图片转化为打印机可以打印的bit
* 效率很高(相对于下面)
* 传进去
* @param bit
* @return
*/
public static byte[] draw2PxPoint(Bitmap bit) {
int nL;
int nH;
int yRow;
int byteSum;//总字节数

yRow = (int) Math.ceil(bit.getHeight()/24);
byteSum = yRow*3*bit.getWidth() + 5*yRow+yRow;

/**
* 分辨率大于256时
* n1=分辨率/256取余(n1%256)
* n2=除得的整数
* 分辨率小于256时
* n1=自身
* n2=0
*/
if(bit.getWidth()<256){
nL = bit.getWidth();
} else {
nL = bit.getWidth()%256;
}
if(bit.getHeight()<256){
nH = 0x00;
} else {
nH = (int) Math.floor(bit.getHeight()/256);
}

byte[] data = new byte[byteSum];
int k = 0;
//对于每一行,逐列打印
for (int j = 0; j < yRow; j++) {
//选择位图模式
data[k++] = 0x1B;
data[k++] = 0x2A;

/**
* m=33时,选择24点双密度打印,分辨率达到200DPI。
* 如果m的值超出了指定的范围,那么nL和之后的数据被当作常规数据处理。
*/
data[k++] = 33; //

/**
* 位图的点数由 nL和 nH指定
* nL和 nH表示水平方向上位图中的点数。通过nL+ nH´ 256计算出点数。
*/
data[k++] = (byte) nL;
data[k++] = (byte) nH;

/**
* d表示位图数据。设置相应的位为 1去打印某点,或设置为 0以不打印某点。
*/
for (int i = 0; i < bit.getWidth(); i++) {
//每一列24个像素点,分为3个字节存储
for (int m = 0; m < 3; m++) {
//每个字节表示8个像素点,0表示白色,1表示黑色
for (int n = 0; n < 8; n++) {
byte b = px2Byte(i, j * 24 + m * 8 + n, bit);
data[k] += data[k] + b;
}
k++;
}
}
//换行
data[k++] = 10;
}
return data;
}

/**
* 图片二值化,黑色是1,白色是0
* @param x 横坐标
* @param y 纵坐标
* @param bit 位图
* @return
*/
public static byte px2Byte(int x, int y, Bitmap bit) {
byte b;
int pixel = bit.getPixel(x, y);
int red = (pixel & 0x00ff0000) >> 16; // 取高两位
int green = (pixel & 0x0000ff00) >> 8; // 取中两位
int blue = pixel & 0x000000ff; // 取低两位
int gray = RGB2Gray(red, green, blue);
if ( gray < 128 ){
b = 1;
} else {
b = 0;
}
return b;
}

/**
* 图片灰度的转化
* @param r
* @param g
* @param b
* @return
*/
private static int RGB2Gray(int r, int g, int b){
int gray = (int) (0.29900 * r + 0.58700 * g + 0.11400 * b); //灰度转化公式
return gray;
}

/**
* base64字符串转位图
* @param base64String
* @return
*/
public static Bitmap base64StringToBitmap(String base64String) {
Bitmap bitmap = null;
String base64Value = "";
try {
String[] strArr =base64String.split(",");
if(strArr.length==1){
base64Value = strArr[0];
} else if(strArr.length==2){
base64Value = strArr[1];
}
byte[] bitmapArray = Base64.decode(base64Value, Base64.DEFAULT);
bitmap = BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.length);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}

/**
* 返回base64字符串
* @param bitmap
* @return
*/
public static String bitmaptoBase64String(Bitmap bitmap) {
//将Bitmap转换成字符串
String string = null;
ByteArrayOutputStream bStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bStream);
byte[] bytes = bStream.toByteArray();
string = Base64.encodeToString(bytes, Base64.DEFAULT);
return string;
}
}

生成二维码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package com.xx.yy.utils;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

/**
* @ClassName: QRCodeUtil
* @Description: 二维码工具类
* Created by Administrator on 2018/11/7.
*/

public class QRCodeUtil {

/**
* 创建条形码位图
* @param content
* @param width
* @param height
* @return
*/
@Nullable
public static Bitmap createBarCodeBitmap(@Nullable String content, int width, int height){
//配置参数
Map<EncodeHintType,Object> hints = new HashMap<>();
// 容错级别 这里选择最高H级别
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);

try{
BitMatrix bitMatrix = new MultiFormatWriter().encode(content,BarcodeFormat.CODE_128, width, height, hints);
/** 1.根据BitMatrix(位矩阵)对象为数组元素赋颜色值 */
int[] pixels = new int[width * height];
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++){
if(bitMatrix.get(x, y)){ // 黑色色块像素设置
pixels[y * width + x] = Color.BLACK;
} else { // 白色色块像素设置
pixels[y * width + x] = Color.WHITE;
}
}
}

/** 4.创建Bitmap对象,根据像素数组设置Bitmap每个像素点的颜色值,之后返回Bitmap对象 */
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);

return bitmap;
} catch (Exception e){
e.printStackTrace();
}
return null;
}


/**
* 创建二维码位图
*
* @param content 字符串内容
* @param size 位图宽&高(单位:px)
* @return
*/
@Nullable
public static Bitmap createQRCodeBitmap(@Nullable String content, int size){
return createQRCodeBitmap(content, size, "UTF-8", "H", "4", Color.BLACK, Color.WHITE, null, null, 0F);
}

/**
* 创建二维码位图 (自定义黑、白色块颜色)
*
* @param content 字符串内容
* @param size 位图宽&高(单位:px)
* @param color_black 黑色色块的自定义颜色值
* @param color_white 白色色块的自定义颜色值
* @return
*/
@Nullable
public static Bitmap createQRCodeBitmap(@Nullable String content, int size, @ColorInt int color_black, @ColorInt int color_white){
return createQRCodeBitmap(content, size, "UTF-8", "H", "4", color_black, color_white, null, null, 0F);
}

/**
* 创建二维码位图 (带Logo小图片)
*
* @param content 字符串内容
* @param size 位图宽&高(单位:px)
* @param logoBitmap logo图片
* @param logoPercent logo小图片在二维码图片中的占比大小,范围[0F,1F]。超出范围->默认使用0.2F
* @return
*/
@Nullable
public static Bitmap createQRCodeBitmap(String content, int size, @Nullable Bitmap logoBitmap, float logoPercent){
return createQRCodeBitmap(content, size, "UTF-8", "H", "4", Color.BLACK, Color.WHITE, null, logoBitmap, logoPercent);
}

/**
* 创建二维码位图 (Bitmap颜色代替黑色) 注意!!!注意!!!注意!!! 选用的Bitmap图片一定不能有白色色块,否则会识别不出来!!!
*
* @param content 字符串内容
* @param size 位图宽&高(单位:px)
* @param targetBitmap 目标图片 (如果targetBitmap != null, 黑色色块将会被该图片像素色值替代)
* @return
*/
@Nullable
public static Bitmap createQRCodeBitmap(String content, int size, Bitmap targetBitmap){
return createQRCodeBitmap(content, size, "UTF-8", "H", "4", Color.BLACK, Color.WHITE, targetBitmap, null, 0F);
}

/**
* 创建二维码位图 (支持自定义配置和自定义样式)
*
* @param content 字符串内容
* @param size 位图宽&高(单位:px)
* @param character_set 字符集/字符转码格式 (支持格式:{@link CharacterSetECI })。传null时,zxing源码默认使用 "ISO-8859-1"
* @param error_correction 容错级别 (支持级别:{@link ErrorCorrectionLevel })。传null时,zxing源码默认使用 "L"
* @param margin 空白边距 (可修改,要求:整型且>=0), 传null时,zxing源码默认使用"4"。
* @param color_black 黑色色块的自定义颜色值
* @param color_white 白色色块的自定义颜色值
* @param targetBitmap 目标图片 (如果targetBitmap != null, 黑色色块将会被该图片像素色值替代)
* @param logoBitmap logo小图片
* @param logoPercent logo小图片在二维码图片中的占比大小,范围[0F,1F],超出范围->默认使用0.2F。
* @return
*/
@Nullable
public static Bitmap createQRCodeBitmap(@Nullable String content, int size,
@Nullable String character_set, @Nullable String error_correction, @Nullable String margin,
@ColorInt int color_black, @ColorInt int color_white, @Nullable Bitmap targetBitmap,
@Nullable Bitmap logoBitmap, float logoPercent){

/** 1.参数合法性判断 */
if(TextUtils.isEmpty(content)){ // 字符串内容判空
return null;
}

if(size <= 0){ // 宽&高都需要>0
return null;
}

try {
/** 2.设置二维码相关配置,生成BitMatrix(位矩阵)对象 */
Hashtable<EncodeHintType, String> hints = new Hashtable<>();

if(!TextUtils.isEmpty(character_set)) {
hints.put(EncodeHintType.CHARACTER_SET, character_set); // 字符转码格式设置
}

if(!TextUtils.isEmpty(error_correction)){
hints.put(EncodeHintType.ERROR_CORRECTION, error_correction); // 容错级别设置
}

if(!TextUtils.isEmpty(margin)){
hints.put(EncodeHintType.MARGIN, margin); // 空白边距设置
}
BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, size, size, hints);

/** 3.根据BitMatrix(位矩阵)对象为数组元素赋颜色值 */
if(targetBitmap != null){
targetBitmap = Bitmap.createScaledBitmap(targetBitmap, size, size, false);
}
int[] pixels = new int[size * size];
for(int y = 0; y < size; y++){
for(int x = 0; x < size; x++){
if(bitMatrix.get(x, y)){ // 黑色色块像素设置
if(targetBitmap != null) {
pixels[y * size + x] = targetBitmap.getPixel(x, y);
} else {
pixels[y * size + x] = color_black;
}
} else { // 白色色块像素设置
pixels[y * size + x] = color_white;
}
}
}

/** 4.创建Bitmap对象,根据像素数组设置Bitmap每个像素点的颜色值,之后返回Bitmap对象 */
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, size, 0, 0, size, size);

/** 5.为二维码添加logo小图标 */
if(logoBitmap != null){
return addLogo(bitmap, logoBitmap, logoPercent);
}

return bitmap;
} catch (WriterException e) {
e.printStackTrace();
}

return null;
}

/**
* 向一张图片中间添加logo小图片(图片合成)
*
* @param srcBitmap 原图片
* @param logoBitmap logo图片
* @param logoPercent 百分比 (用于调整logo图片在原图片中的显示大小, 取值范围[0,1], 传值不合法时使用0.2F)
* 原图片是二维码时,建议使用0.2F,百分比过大可能导致二维码扫描失败。
* @return
*/
@Nullable
private static Bitmap addLogo(@Nullable Bitmap srcBitmap, @Nullable Bitmap logoBitmap, float logoPercent){

/** 1. 参数合法性判断 */
if(srcBitmap == null){
return null;
}

if(logoBitmap == null){
return srcBitmap;
}

if(logoPercent < 0F || logoPercent > 1F){
logoPercent = 0.2F;
}

/** 2. 获取原图片和Logo图片各自的宽、高值 */
int srcWidth = srcBitmap.getWidth();
int srcHeight = srcBitmap.getHeight();
int logoWidth = logoBitmap.getWidth();
int logoHeight = logoBitmap.getHeight();

/** 3. 计算画布缩放的宽高比 */
float scaleWidth = srcWidth * logoPercent / logoWidth;
float scaleHeight = srcHeight * logoPercent / logoHeight;

/** 4. 使用Canvas绘制,合成图片 */
Bitmap bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(srcBitmap, 0, 0, null);
canvas.scale(scaleWidth, scaleHeight, srcWidth/2, srcHeight/2);
canvas.drawBitmap(logoBitmap, srcWidth/2 - logoWidth/2, srcHeight/2 - logoHeight/2, null);

return bitmap;
}
}

使用注意

  • 有些设备使用蓝牙连接的时候木有出现 配对 这个过程,而且连接成功了,这个是正确的。

  • 官方的此方法只接受base64格式的值

    1
    BLEPlxMag.writeCharacteristicWithResponseForDevice(deviceId,serviceUUID,characteristicUUID,base64Value)
  • 分包,数据如果过多必须进行分包处理、分包大小取决设备

    1
    2
    3
    4
    5
    6
    7
    8
    toWrite = iconv.encode(item.values, 'GBK');
    packetCount = Math.ceil(toWrite.length / packetSize);
    for (var i = 0; i < packetCount; i++) {
    packet = new Buffer(packetSize)
    toWrite.copy(packet, 0, i * packetSize, (i + 1) * packetSize)
    base64Value = packet.toString('base64');
    writePromises.push(blePlxWrite(id,base64Value))
    }

使用过程

低功耗蓝牙通信数据慢、过程比较繁琐

相关链接

  1. react-native-ble-plx
  2. 热敏打印机指令集