热敏蓝牙打印机开发
Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解`
热敏蓝牙打印机开发
日期:2019-11-5 阿珏 折腾代码 浏览:2359次 评论:14条
最近在做小票打印这块,项目需求是IOS和安卓两种都要实现,开始做的时候也是一脸懵,然后网上找了不少资料,踩了一堆坑,看了好多文章,结果还好成了蓝牙打印机一般分为两种打印模式,票据打印、标签打印
公司买的渣渣打印机连开发文档都没有,害我走了不少坑,让我开发买的时候也不咨询咨询我
目前微信小程序连接蓝牙打印机 wx.createBLEConnection 测试在IOS设备上没有问题,在部分安卓手机上会出现异常(表现为,连接是会弹出系统配对框,不管点取消还是输入配对码后点确定,都会立马断开连接。如果不输入也不取消则会在30秒以内自动断开蓝牙打印机)
现在采用的方式是各给安卓和IOS写一套蓝牙打印的命令
IOS
if (app.sysinfo.provider == 1) {
// 开启蓝牙
app.onBluetooth()
setTimeout(() => {
this.android_search()
}, 2000)
return false;
}
this.closeBluetoothAdapter()
uni.openBluetoothAdapter({
success: (res) => {
console.log("初始化蓝牙模块: " + JSON.stringify(res));
this.startBluetoothDevicesDiscovery()
},
fail: (res) => {
if (res.errCode === 10001) {
uni.onBluetoothAdapterStateChange((res) => {
console.log('监听蓝牙适配器状态变化事件', res)
if (res.available == false) {
app.global_printing = {}
this.connected = false
this.chs = []
this.canWrite = false
}
if (res.available) {
this.startBluetoothDevicesDiscovery()
}
})
}
if (res.errCode) {
app.alert('初始化蓝牙失败,错误码:' + res.errCode)
return false;
}
app.alert(res.errMsg)
}
})
})
})
const ds = e.currentTarget.dataset
const deviceId = ds.deviceId
const name = ds.name
if (app.sysinfo.provider == 1) {
if (ds.pair !== true) {
this.android_search(deviceId)
} else {
console.log('已配对')
}
var device = null,
BAdapter = null,
BluetoothAdapter = null,
uuid = null,
main = null,
bluetoothSocket = null;
var mac_address = deviceId
var main = plus.android.runtimeMainActivity();
BluetoothAdapter = plus.android.importClass("android.bluetooth.BluetoothAdapter");
var UUID = plus.android.importClass("java.util.UUID");
uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
BAdapter = BluetoothAdapter.getDefaultAdapter();
device = BAdapter.getRemoteDevice(mac_address);
plus.android.importClass(device);
bluetoothSocket = device.createInsecureRfcommSocketToServiceRecord(uuid);
plus.android.importClass(bluetoothSocket);
if (!bluetoothSocket.isConnected()) {
console.log('检测到设备未连接,尝试连接....');
bluetoothSocket.connect();
}
this.connected = true
this.name = name
this.deviceId = deviceId
this.canWrite = true
app.global_printing = {
name: name,
deviceId: deviceId
}
app.saveData1('global_printing', app.global_printing)
uni.hideLoading();
return false;
}
uni.createBLEConnection({
deviceId,
success: (res) => {
this.connected = true
this.name = name
this.deviceId = deviceId
app.global_printing = {
name: name,
deviceId: deviceId
}
this.onBLEConnectionStateChange()
// 防止获取失败
setTimeout(() => {
this.getBLEDeviceServices(deviceId)
}, 1000)
},
fail: (res) => {
uni.hideLoading();
app.Toast('设备连接失败')
console.log("蓝牙连接失败:", res);
}
})
this.stopBluetoothDevicesDiscovery()
app.saveData1('global_printing', app.global_printing)
//this.writeBLECharacteristicValue()
}
if (item.properties.notify || item.properties.indicate) {
uni.notifyBLECharacteristicValueChange({
deviceId,
serviceId,
characteristicId: item.uuid,
state: true,
})
}
}
},
fail(res) {
console.error('获取特征值失败:', res)
}
})
// 操作之前先监听,保证第一时间获取数据
uni.onBLECharacteristicValueChange((characteristic) => {
console.log(this.data.chs);
const idx = this.inArray(this.data.chs, 'uuid', characteristic.characteristicId)
const data = {}
if (idx === -1) {
this.chs[this.data.chs.length] = {
uuid: characteristic.characteristicId,
value: ab2hex(characteristic.value)
}
} else {
this.chs[idx] = {
uuid: characteristic.characteristicId,
value: ab2hex(characteristic.value)
}
}
})
蓝牙连接状态改变device ${res.deviceId} state has changed, connected: ${res.connected}
setTimeout(function() {
app.printing_status = false
console.log('打印结束');
}, 1000);
Android
就相对简单方便,采用Native.js直接调用Native Java接口通道,通过plus.android调用安卓原生系统API。
原生安卓文档
https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter?hl=en
filter.addAction(bdevice.ACTION_FOUND);
filter.addAction(BAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BAdapter.ACTION_DISCOVERY_FINISHED);
filter.addAction(BAdapter.ACTION_STATE_CHANGED);
main.registerReceiver(receiver, filter); //注册监听
var that = this;
// 打印
var device = null,
BAdapter = null,
BluetoothAdapter = null,
uuid = null,
main = null,
bluetoothSocket = null;
var mac_address = app.global_printing.deviceId
var main = plus.android.runtimeMainActivity();
BluetoothAdapter = plus.android.importClass("android.bluetooth.BluetoothAdapter");
var UUID = plus.android.importClass("java.util.UUID");
uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
BAdapter = BluetoothAdapter.getDefaultAdapter();
try {
device = BAdapter.getRemoteDevice(mac_address);
plus.android.importClass(device);
bluetoothSocket = device.createInsecureRfcommSocketToServiceRecord(uuid);
plus.android.importClass(bluetoothSocket);
} catch (e) {
console.log('asasssds-d=da=da-dsd');
app.printing_status = false
app.alert('打印失败')
return false;
}
if (!bluetoothSocket.isConnected()) {
console.log('检测到设备未连接,尝试连接....');
bluetoothSocket.connect();
}
console.log('设备已连接');
if (bluetoothSocket.isConnected()) {
var outputStream = bluetoothSocket.getOutputStream();
plus.android.importClass(outputStream);
for (var i = 0; i < arr.length; i++) {
outputStream.write(arr[i]);
}
outputStream.flush();
device = null //这里关键
bluetoothSocket.close(); //必须关闭蓝牙连接否则意外断开的话打印错误
}
setTimeout(function() {
app.printing_status = false
console.log('打印结束');
}, 1000);
打印指令
(更多打印指令参考 https://www.jianshu.com/p/dd6ca0054298)
/**
- 复位打印机
*/
public static final byte[] RESET = {0x1b, 0x40};
/**
- 左对齐
*/
public static final byte[] ALIGN_LEFT = {0x1b, 0x61, 0x00};
/**
- 中间对齐
*/
public static final byte[] ALIGN_CENTER = {0x1b, 0x61, 0x01};
/**
- 右对齐
*/
public static final byte[] ALIGN_RIGHT = {0x1b, 0x61, 0x02};
/**
- 选择加粗模式
*/
public static final byte[] BOLD = {0x1b, 0x45, 0x01};
/**
- 取消加粗模式
*/
public static final byte[] BOLD_CANCEL = {0x1b, 0x45, 0x00};
/**
- 宽高加倍
*/
public static final byte[] DOUBLE_HEIGHT_WIDTH = {0x1d, 0x21, 0x11};
/**
- 宽加倍
*/
public static final byte[] DOUBLE_WIDTH = {0x1d, 0x21, 0x10};
/**
- 高加倍
*/
public static final byte[] DOUBLE_HEIGHT = {0x1d, 0x21, 0x01};
/**
- 字体不放大
*/
public static final byte[] NORMAL = {0x1d, 0x21, 0x00};
/**
- 设置默认行间距
*/
public static final byte[] LINE_SPACING_DEFAULT = {0x1b, 0x32};
关于二维码的打印
通过上面的文章我们可以知道
我们需要读取生成后的二维码的像素点的rgba,再将图片数据先4合1判断0还是1(0代表打印1代表不打印),紧接着八合1,因为一个字节有8位。最后使用打印机的位图指令逐行扫描打印
4合1
本想着二维码不是黑就是白,肯定不是255就是0,其实还是会有一小部分是其他数值的,这个要注意哦,每4位是一个像素点的rgba,然后黑白色的rgb就是(0,0,0)和(255,255,255),所以每四位只把第一位黑白化,然后将每四位的第一位取出来作为新的数组,当rule>200的时候,值取0,表示不打印,否则取1,表示打印;
8合1
假如我们取出来的8位数是[0,0,0,0,0,0,0,1],这个时候8合1,我们需要进行进制转换,从右往左是2的零次方,2的一次方,等等,依次上加,实际是 0 * 27 + 0 * 26 + 0 * 25 + 0 * 24 + 0 * 23 + 0 * 22 + 0 * 21 + 1 * 20,这个数就是我们要的最终数据的其中之一。
将数据转换成ArrayBuffer,其次打印必须要有指令!参考网址以及标准的ESC-POS指令集,下面代码中的数字都是指令,另外,由于我这边的打印机支持的是gb2312格式,所以在转成ArrayBuffer的同时,还需要把编码格式转成正确的格式。
不过有一点我是要说下的,要注意ios和安卓的不同,安卓一次只能写入不超过20字节(ios具体不清楚,目测120字节),建议是直接截取数据data.slice(20, byteLength),打印成功再次回调,循环打印。
qr(text,callback) {
let that = this;
const ctx = uni.createCanvasContext('myQrcode');
ctx.clearRect(0, 0, 240, 240);
drawQrcode({
canvasId: 'myQrcode',
text: String(text),
width: 120,
height: 120,
callback(e) {
// setTimeout(() => {
// 获取图片数据
uni.canvasGetImageData({
canvasId: 'myQrcode',
x: 0,
y: 0,
width: 240,
height: 240,
success(res) {
let arr = that.convert4to1(res.data);
let data = that.convert8to1(arr);
const cmds = [].concat([27, 97, 1], [29, 118, 48, 0, 30, 0,
240, 0
],
data, [27, 74, 3], [27, 64]);
const buffer = toArrayBuffer(Buffer.from(cmds, 'gb2312'));
// 二维码
for (let i = 0; i < buffer.byteLength; i = i + 120) {
that.arrPrint.push(buffer.slice(i, i + 120));
}
callback()
}
})
// }, 3000);
}
});
},
// 二维码
1、toArrayBuffer ,是个组件,要安装的,https://www.npmjs.com/package/to-array-buffer 或者你用这种写法也可以const buffer = new Uint8Array(Buffer.from(cmds, 'gb2312')).buffer;
2、注意查看自己的数据是否正确,画图的数据有问题的话,也可能打印出黑块;
3、数据要算!!!要算!!要算!! ,比如我画图是160*160 ,然后我打印数据拼接的指令[29, 118, 48, 0, 20, 0, 160, 0]这个里面的20和160 这个就是算的,参考上方文章看下原因,大概就是1:8,然后画图和读图的数据一致
相关函数
(经过反复测试得出,打印纸一行最大字节数是32字节,这里指的是普通的票据打印机)
打印三列或者两列,是需要自己计算空格进行填充,没有现成的指令噢
总宽度 - 左侧文字长度 - 右侧文字长度 就是空格的长度。
/**
-
打印两列
-
@param leftText 左侧文字
-
@param rightText 右侧文字
-
@return
*/
printTwoData(leftText, rightText) {
var sb = ''
var leftTextLength = this.getBytesLength(leftText);
var rightTextLength = this.getBytesLength(rightText);
sb += leftText// 计算两侧文字中间的空格
var marginBetweenMiddleAndRight = 32 - leftTextLength - rightTextLength;for (var i = 0; i < marginBetweenMiddleAndRight; i++) {
sb += ' '
}
sb += rightText
return sb.toString();
},
/** -
打印三列
-
@param leftText 左侧文字
-
@param middleText 中间文字
-
@param rightText 右侧文字
-
@return
*/
printThreeData(leftText, middleText, rightText) {
var sb = ''
// 左边最多显示 8 个汉字 + 两个点
if (leftText.length > 8) {
leftText = leftText.substring(0, 8) + "..";
}
var leftTextLength = this.getBytesLength(leftText);
var middleTextLength = this.getBytesLength(middleText);
var rightTextLength = this.getBytesLength(rightText);sb += leftText
// 计算左侧文字和中间文字的空格长度
var marginBetweenLeftAndMiddle = 20 - leftTextLength - middleTextLength / 2;for (var i = 0; i < marginBetweenLeftAndMiddle; i++) {
sb += ' '
}
sb += middleText// 计算右侧文字和中间文字的空格长度
var marginBetweenMiddleAndRight = 12 - middleTextLength / 2 - rightTextLength;for (var i = 0; i < marginBetweenMiddleAndRight; i++) {
sb += ' '
}sb += rightText
// 打印的时候发现,最右边的文字总是偏右一个字符,所以需要删除一个空格
// sb.delete(sb.length() - 1, sb.length()).append(rightText);
return sb.toString();
},
max(n1, n2) {
return Math.max(n1, n2)
},
len(arr) {
arr = arr || []
return arr.length
},
//4合1
convert4to1(res) {
let arr = [];
for (let i = 0; i < res.length; i++) {
if (i % 4 == 0) {
let rule = 0.29900 * res[i] + 0.58700 * res[i + 1] + 0.11400 * res[i + 2];
if (rule > 200) {
res[i] = 0;
} else {
res[i] = 1;
}
arr.push(res[i]);
}
}
return arr;
},
//8合1
convert8to1(arr) {
let data = [];
for (let k = 0; k < arr.length; k += 8) {
let temp = arr[k] * 128 + arr[k + 1] * 64 + arr[k + 2] * 32 + arr[k + 3] * 16 + arr[k + 4] * 8 +
arr[k + 5] * 4 +
arr[k + 6] * 2 + arr[k + 7] * 1
data.push(temp);
}
return data;
},
inArray(arr, key, val) {
for (let i = 0; i < arr.length; i++) {
if (arr[i][key] === val) {
return i;
}
}
return -1;
},
// ArrayBuffer转16进度字符串示例
ab2hex(buffer) {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
},
// 计算文字占用长度
getBytesLength(str) {
var num = str.length; //先用num保存一下字符串的长度(可以理解为:先假设每个字符都只占用一个字节)
for (var i = 0; i < str.length; i++) { //遍历字符串
if (str.charCodeAt(i) > 255) { //判断某个字符是否占用两个字节,如果是,num再+1
num++;
}
}
return num; //返回最终的num,既是字符串总的字节长度
}


代码大多是直接从项目中copy过来的,没有整理过,并不能直接运行,仅供参考
不是我那一卷打印失败的打印纸丢了,不然就让你们看看什么叫做 第一次做打印机开发的程序员
网友评论:
深圳自考 7个月前 (2020-10-08)
厉害了 简直一模一样 以后可以开一家不饿了么公司了[#aru_1]
一朵时光红 1年前 (2020-03-21)
腾云奇袭技能释放时,神明将跳到筋斗云上,并留下一个完美的分身
李鱼儿啊 1年前 (2019-12-27)
楼主,向打印机发送给buffer的地方的代码可以详细一点嘛?我目前卡在这一步 很迷惑 可以加我QQ 2930962607
阿珏 1年前 (2019-12-28)
@李鱼儿啊:你得说清楚你的问题
李鱼儿啊 1年前 (2019-12-29)
@阿珏:我是uniApp的自定义组件模式开发 我是使用的native.js连接的Android手机的蓝牙 页面使用的是web-view的子窗口加载的本地html文件 因为需要使用html2canvas.js插件来截取页面生成图片 在插件的回调方法中可以获取到canvas的dom对象 因此可以获取到imageData的像素点信息 然后我使用你写得4合1 以及 8合1 生成数据 然后在发送到打印机的write方法时 我写了try catch 来捕获发送时的异常 发现问题出现在生成的数据上面 有时候catch会报错讲试图获取一个null Array的长度 有时候会直接报错一个空{} json对象 比较重要的点是 你在buffer数据的转换上面使用到了 node.js 的Buffer对象 但是我这边并不能使用 希望楼主可以帮忙解答一下 方便的话加一下QQ 2930962607
阿珏 1年前 (2019-12-30)
@李鱼儿啊:很抱歉,关于这个Android打印图片数据的问题,我这边也没有完全解决掉,没办法给你更好的解决方案。Android打印这块可以参考一下GoogleAndroid官方的文档,发送数据处理这块应该是有问题的。
『乐 易』 2年前 (2019-11-16)
这,不是类似于喵喵机的吗,正好我买个了喵喵机P1[#aru_1]
阿珏 2年前 (2019-11-17)
@『乐 易』:这,貌似不是很一样,而且那不是叫咕咕机吗[#aru_2]
『乐 易』 2年前 (2019-11-17)
@阿珏:功能一样,只不过喵喵机小一点而已,而且是喵喵机不是咕咕鸡[#aru_38]
阿珏 2年前 (2019-11-18)
@『乐 易』:好吧好吧,原来还有一个叫喵喵机的玩意[#aru_16]
mengkun 2年前 (2019-11-05)
很强!有饿了么订单条内味儿了[#aru_53]
阿珏 2年前 (2019-11-05)
@mengkun:我们是不饿了么[#aru_42]
repostone 2年前 (2019-11-05)
非技术的路过。
阿珏 2年前 (2019-11-05)
@repostone:牛逼就完事了[#aru_43]
作者: 阿珏酱
出处:https://www.cnblogs.com/Ajue/p/18202527
本博客所有文章如无特别注明均为原创。本站使用「CC BY-NC-SA 4.0」创作共享协议,引用或转载请以超链接形式注明作者及出处。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战