机型等参数
- HSPOS
- 点密度:576点/行(8dots/mm,203dpi)
- 接口类型: 蓝牙(Bluetooth2.0,4.0双模,支持Android,IOS)
- 打印方式:图形打印(位图)
- 打印指令集: ESC/POS
基本思路
1、 实现蓝牙连接
**B12.js方法封装
class BluetoothTools {
constructor() {
this.decimalData = []; //获取buffer之前的二进制数据集合;
this.deviceId = null;
this.initStatus = 0; //蓝牙初始化结果;0:初始化中,1-成功;2-失败;
this.linked = false; //蓝牙是否为连接状态;
this.connectChangeHandler = null;
}
// 初始化;
init(connectChangeHandler) {
this.connectChangeHandler = connectChangeHandler;
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: res => {
this.initStatus = 1;
this.onConnectChange(connectChangeHandler)
resolve(res)
},
fail: err => {
this.initStatus = 2;
console.log('蓝牙初始化失败:', err);
this.toast('蓝牙不可用!')
reject(err)
}
})
})
}
// 获取蓝牙状态,是否可用
checkCanUse() {
return new Promise((resolve, reject) => {
uni.getBluetoothAdapterState({
success: res => {
// console.log(res);
resolve(res)
},
fail: err => {
// console.log(err);
reject(err)
}
})
})
}
// 开始搜索蓝牙
startSearch(cb) {
return new Promise(async (resolve, reject) => {
// if (this.initStatus === 2) {
// this.toast('蓝牙初始化失败!');
// reject();
// return false;
// } else if (this.initStatus === 0) {
// this.toast('蓝牙未初始化!');
// reject();
// return false;
// }
if (this.initStatus !== 1) {
await this.init(this.connectChangeHandler);
}
this.onFound(cb);
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
services: [],
success: (res) => {
// console.log('开始搜索蓝牙设备!', res);
resolve(res)
},
fail: err => {
// console.log('开启搜索失败:', err);
this.toast('开始搜索失败!')
reject(err)
}
})
})
}
// 监听单一设备搜索结果
onFound(cb) {
uni.onBluetoothDeviceFound(res => {
// console.log('蓝牙设备:', res.devices);
cb(res.devices)
})
}
// 停止搜索;
stopSearch() {
return new Promise((resolve, reject) => {
uni.stopBluetoothDevicesDiscovery({
success: (res) => {
resolve(res)
},
fail: (err) => {
// this.toast('蓝牙停止搜索失败,请重试!');
console.log('蓝牙停止搜索失败:', err);
reject(err)
}
})
})
}
// 关闭蓝牙模块(打印完后调用)
closeAdapter() {
return new Promise((resolve, reject) => {
uni.closeBluetoothAdapter({
success: res => {
resolve(res);
},
fail: (err) => {
console.log('关闭失败:', err);
// this.toast('蓝牙模块关闭失败!')
reject(err);
}
})
})
}
toast(msg) {
uni.showToast({
title: msg,
icon: 'none',
duration: 2200
})
}
// 根据deviceId连接蓝牙;
connectBLE(deviceId) {
return new Promise(async (resolve, reject) => {
if (this.initStatus !== 1) {
await this.init(this.connectChangeHandler).catch(err => {
reject(err)
});
}
this.checkCanUse().then(async data => {
// console.log(data);
if (data.available) {
uni.createBLEConnection({
deviceId: deviceId,
success: (res) => {
resolve(res)
},
fail: err => {
console.log('蓝牙连接失败!', err);
this.toast('蓝牙连接失败,请重试!')
reject(err)
}
})
} else {
this.toast('蓝牙不可用')
reject()
}
}).catch(err => {
this.toast('蓝牙不可用!!')
reject()
})
})
}
// 根据deviceId断开蓝牙链接;
closeBLE(deviceId) {
return new Promise((resolve, reject) => {
if (!deviceId) {
resolve();
return;
}
uni.closeBLEConnection({
deviceId: deviceId,
success: (res) => {
// console.log('蓝牙已断开!', res);
resolve(res)
},
fail: err => {
console.log('蓝牙断开失败!', err);
// this.toast('蓝牙断开失败, 请重试!')
reject(err)
}
})
})
}
// 监听蓝牙连接状态;
onConnectChange(cb) {
uni.onBLEConnectionStateChange(res => {
console.log('监听蓝牙连接状态:', res);
if (res.connected) {
this.linked = true;
this.deviceId = res.deviceId;
} else {
this.linked = false;
this.deviceId = null;
}
cb(res);
})
}
}
export default BluetoothTools;
*** vue文件中内容
<template>
<view class="">
<view class="bluetooth_container">
<view class="title">
已配对设备
</view>
<view class="matched_list" v-if="matchedList && matchedList.length">
<view class="flex" v-for="(item, index) in matchedList" :key="item.deviceId" @longpress="showModal2(item.deviceId)">
<image class="img" src="/static/print.png" mode=""></image>
<text class="name">{{item.localName ? item.localName : item.name ? item.name : '--'}}</text>
<text class="link linked" @click="showModal" v-if="linkedDeviceId === item.deviceId">已连接</text>
<block v-else>
<image v-if="selectedDeviceId==item.deviceId && isConnectting" class="load_img" src="/static/loadding.gif" mode=""></image>
<text v-else class="link unlink" @click="connectHandler(item)">未连接</text>
</block>
</view>
</view>
<view class="empty_box" v-else>
<u-empty mode="search" text="暂无配对设备,快快添加吧~"></u-empty>
</view>
<view class="block"></view>
<view class="title search_title">
<text>扫描可用设备</text>
<view class="stop" v-if="isSearching" @click="stopSearch">
<text>停止</text>
</view>
<view class="img_box" v-else @click="startSearch">
<image class="img img1" src="/static/refresh.png" mode=""></image>
</view>
<view class="load_box" v-if="isSearching">
<image class="load_img" src="/static/loadding.gif" mode=""></image>
</view>
</view>
<view class="list">
<view class="flex" v-for="(item, index) in deviceList" :key="index">
<image class="img img2" src="/static/print.png" mode=""></image>
<text class="name">{{item.localName ? item.localName : item.name ? item.name : '--'}}</text>
<image v-if="selectedDeviceId==item.deviceId && isConnectting" class="load_img" src="/static/loadding.gif" mode=""></image>
<text v-else class="link" @click="connectHandler(item,index)">连接</text>
</view>
</view>
</view>
<u-modal v-model="modalShow" show-cancel-button content="是否断开连接?" @confirm="closeConnect"></u-modal>
<u-modal v-model="modalShow2" show-cancel-button content="是否忽略此设备?" @confirm="deleteDevice"></u-modal>
</view>
</template>
<script>
import B12s from '@/common/b12s.js';
const b12s = new B12s();
export default {
data() {
return {
// 蓝牙相关;
matchedList: [], //已配对的列表;
deviceList: [], //搜索到的设备列表;
initCode: 0, //蓝牙初始化结果;0:初始化中,1-成功;2-失败;
selectedDeviceId: '', //当前操作的蓝牙设备id;
isConnectting: false, //蓝牙正在连接中;
linkedDeviceId: '', //已连接的蓝牙mac地址;
isSearching: false, //是否正在搜索蓝牙设备;
b12s: null, //蓝牙工具;
}
},
async onLoad(options) {
this.initBluetooth();
},
async onUnload() {
this.stopSearch();
b12s.closeBLE(this.linkedDeviceId);
b12s.closeAdapter();
},
methods: {
// 初始化蓝牙;
async initBluetooth() {
this.getMatchedList();
await b12s.init(this.onConnectChange);
this.autoConnect();
},
// 如果有配对历史,自动连接最后一个;
autoConnect() {
let length = this.matchedList.length
if (length <= 0) return;
let item = this.matchedList[length - 1];
this.connectHandler(item);
},
// 开始搜索蓝牙;
async startSearch() {
await b12s.startSearch(this.getDeviceList);
this.isSearching = true;
},
// 停止搜索
async stopSearch() {
await b12s.stopSearch();
this.isSearching = false;
},
// 获取历史配对列表;
getMatchedList() {
let list = uni.getStorageSync('__bluetooth_list_');
if (list) {
this.matchedList = list;
}
console.log(this.matchedList);
},
// 获取可用设备列表;
getDeviceList(devices) {
for (let i = 0; i < devices.length; i++) {
let name = devices[i].name || devices[i].localName;
if (name) {
let dLIndex = this.deviceList.findIndex(item => item.deviceId === devices[i].deviceId);
let mLIndex = this.matchedList.findIndex(item => item.deviceId === devices[i].deviceId);
if (dLIndex < 0 && mLIndex < 0) {
this.deviceList.push({
name: name,
deviceId: devices[i].deviceId
})
}
}
}
},
// 点击连接;
async connectHandler(item, index = -1) {
this.stopSearch();
await b12s.closeBLE(this.linkedDeviceId)
this.selectedDeviceId = item.deviceId;
this.isConnectting = true;
await b12s.connectBLE(item.deviceId).catch(err => {
this.isConnectting = false;
});
// this.linkedDeviceId = item.deviceId;
this.isConnectting = false;
let indexRes = this.matchedList.findIndex(itm => itm.deviceId === item.deviceId);
if (indexRes < 0) {
this.matchedList.push(item);
index !== -1 && this.deviceList.splice(index, 1);
this.saveStorage()
}
},
// 断开连接
closeConnect() {
b12s.closeBLE(this.linkedDeviceId);
this.modalShow = false;
},
// 监听蓝牙连接状态;
onConnectChange(res) {
if (res.connected) {
// this.$u.toast('蓝牙已连接');
this.linkedDeviceId = res.deviceId;
} else {
// this.$u.toast('蓝牙已断开');
this.linkedDeviceId = '';
}
},
// 删除已缓存设备;
deleteDevice() {
this.matchedList = this.matchedList.filter(item => item.deviceId !== this.selectedDeviceId);
this.saveStorage()
},
// 缓存已配对蓝牙;
saveStorage() {
uni.setStorageSync('__bluetooth_list_', this.matchedList)
}
}
}
</script>
<style lang="scss" scoped>
.bluetooth_container {
padding: 30rpx;
.load_img {
width: 40rpx;
height: 40rpx;
}
.title {
font-size: 34rpx;
font-weight: bold;
.img {
margin-left: 20rpx;
vertical-align: middle;
width: 36rpx;
height: 36rpx;
}
}
.search_title {
display: flex;
justify-content: space-between;
margin-top: 60rpx;
.img_box {
flex: 1;
}
.load_img {
width: 40rpx;
height: 40rpx;
}
.stop {
flex: 1;
margin-left: 30rpx;
text {
font-size: 24rpx;
font-weight: normal;
background-color: #fa3534;
color: #ffffff;
padding: 10rpx 20rpx;
border-radius: 30rpx;
}
}
}
.matched_list {
// padding: 30rpx 0;
.flex {
display: flex;
margin: 30rpx 0;
justify-content: space-between;
align-items: center;
.img {
width: 40rpx;
height: 40rpx;
}
.name {
flex: 1;
margin-left: 40rpx;
}
.link {
padding: 10rpx 40rpx;
background-color: #F2F2F2;
border-radius: 40rpx;
font-size: 28rpx;
}
.unlink {
color: #606266;
}
.linked {
color: #19be6b;
}
}
}
.empty_box {
padding: 90rpx 0;
}
.list {
.flex {
display: flex;
margin: 30rpx 0;
justify-content: space-between;
align-items: center;
.img {
width: 40rpx;
height: 40rpx;
}
.name {
flex: 1;
margin-left: 40rpx;
}
.link {
padding: 10rpx 40rpx;
background-color: #F2F2F2;
border-radius: 40rpx;
color: #2979ff;
font-size: 28rpx;
}
}
}
.btns {
display: flex;
justify-content: space-around;
margin-top: 200rpx;
.btn {
margin: 0;
width: 40%;
}
}
}
</style>
2、获取位图信息
vue页面中拿到像素(位图)信息;
<template>
<view class="">
<canvas id="canvas" canvas-id="canvas" class="canvas"></canvas>
</view>
</template>
<script>
import B12s from '@/common/b12s.js';
const b12s = new B12s();
export default {
methods: {
//画图;
drawImage() {
const ctx = uni.createCanvasContext('canvas');
uni.chooseImage({
success: res => {
ctx.drawImage(res.tempFilePaths[0], 0, 0, 150, 100)
ctx.draw()
}
})
},
// 获取canvas的像素信息;
getImageInfo() {
return new Promise((resolve, reject) => {
uni.canvasGetImageData({
canvasId: 'canvas',
x: 0,
y: 0,
width: _this.canvas2ImgWidth,
height: _this.canvas2ImgHeight,
success(res) {
resolve(res)
},
fail: err => {
this.$u.toast('获取图片数据失败')
reject(err)
}
})
})
},
async printHandler() {
uni.hideLoading();
uni.showLoading({
title: '打印中',
mask: true
})
const res = await this.getImgInfo();
b12s.printImage(res)
}
}
}
</script>
3、开始打印;
b12s.js
class BluetoothTools {
constructor() {
this.commands = {
init: [0x1b, 0x40], // 清理buffer数据,重置模式;ASC2: ESC @
print: [0x0a], //开始打印;
printL5: [0x1b, 0x64, 0x05],
printL6: [0x1b, 0x64, 0x06],
printL8: [0x1b, 0x64, 0x08],
printL10: [0x1b, 0x64, 0x10],
}
this.decimalData = []; //获取buffer之前的二进制数据集合;
this.deviceId = null;
this.initStatus = 0; //蓝牙初始化结果;0:初始化中,1-成功;2-失败;
this.linked = false; //蓝牙是否为连接状态;
this.timer = null;
this.writeTime = 0;
this.byteLength = this.isAndroid() ? 200 : 500;
this.connectChangeHandler = null;
}
/**
* 低功耗蓝牙API
*/
// 根据deviceId 设置传输字节最大值;
setMTU(deviceId) {
return new Promise((resolve, reject) => {
uni.setBLEMTU({
deviceId: deviceId,
mtu: 512,
success: (res) => {
console.log('设置MTu成功', res);
resolve(res)
},
fail: (err) => {
console.log('设置mtu失败', err);
this.toaset('设置mtu失败!')
reject(err)
}
})
})
}
// 根据deviceId/mac地址 查询serviceId列表;
getServiceId(deviceId) {
return new Promise((resolve, reject) => {
uni.getBLEDeviceServices({
deviceId: deviceId,
success: res => {
resolve(res)
},
fail: err => {
console.log('获取serviceId失败', err);
reject(err)
}
})
})
}
// 根据deviceId 和 serviceId 获取特征Id;
getCharId(deviceId, serviceId) {
return new Promise((resolve, reject) => {
uni.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: serviceId,
success: res => {
resolve(res)
},
fail: err => {
console.log('获取特征id失败', err);
reject(err)
}
})
})
}
// 写入数据;
writeBLE(deviceId, serviceId, charId, buffer) {
// console.log(deviceId, serviceId, charId, buffer.byteLength);
// return;
let _this = this;
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId: deviceId, //设备Id、mac地址;
serviceId: serviceId, //服务id;
characteristicId: charId, //特征id;
value: buffer, //指令buffer数据;
writeType: 'write', //'writeNoResponse',
success: res => {
// console.log('数据写入成功', res);
_this.writeTime = 0;
resolve(res)
},
fail: err => {
console.log('写入失败:', Date.now());
reject(err);
}
})
})
}
/**
* 具体功能实现
*/
// 打印传入的位图信息
async printImage(res) {
if (!this.deviceId) {
this.toast('未检测到蓝牙设备id');
this.hideLoading()
return;
}
this.isAndroid() && await this.setMTU(this.deviceId);
const imgArr = this.getImgArray(res);
// return;
const { serviceId, charId } = await this.getWriteIds(this.deviceId);
this.startPrint(this.deviceId, serviceId, charId, imgArr);
}
// 开始打印;
async startPrint(deviceId, serviceId, charId, cmd) {
// 初始化;
let initArr = Array.from(this.commands.init).concat(Array.from(this.commands.print));
let initBuffer = new Uint8Array(initArr).buffer;
await this.writeMidWare(deviceId, serviceId, charId, initBuffer)
// let cmds = Array.from(this.commands.init).concat(Array.from(cmd)).concat(Array.from(this.commands.printL10));
// 分别传输指令;
let cmds = Array.from(cmd);
console.log(cmds.length);
if (cmds.length > this.byteLength) {
let cmdArrs = [];
let newCmds = Array.from(cmds);
let length = Math.ceil(cmds.length / this.byteLength);
for (let i = 0; i < length; i++) {
cmdArrs.push(newCmds.slice(this.byteLength * i, this.byteLength * (i + 1)));
}
for (let i = 0; i < cmdArrs.length; i++) {
console.log(i, cmdArrs.length);
let buffer = new Uint8Array(cmdArrs[i]).buffer;
await this.writeMidWare(deviceId, serviceId, charId, buffer)
}
} else {
let buffer = new Uint8Array(cmds).buffer;
cmds.length && await this.writeMidWare(deviceId, serviceId, charId, buffer)
}
// 打印空行;
let printLCmd = Array.from(this.commands.printL5);
let printLBuffer = new Uint8Array(printLCmd).buffer;
await this.writeMidWare(deviceId, serviceId, charId, printLBuffer);
}
// 处理安卓写入失败的问题;
writeMidWare(deviceId, serviceId, charId, buffer) {
let _this = this;
return new Promise((resolve, reject) => {
this.writeBLE(deviceId, serviceId, charId, buffer).then(res => {
resolve(res)
}).catch(err => {
// console.log(111111, _this.writeTime);
if (_this.writeTime < 50) {
_this.writeTime++;
return new Promise((reso, reje) => {
clearTimeout(this.timer)
this.timer = setTimeout(() => {
reso(this.writeMidWare(deviceId, serviceId, charId, buffer))
}, 100)
})
} else {
_this.writeTime = 0;
_this.hideLoading();
_this.toast('数据写入失败,请走纸后重试!');
reject(err);
}
}).then(res => {
resolve(res)
})
})
}
// 位图信息转换为打印指令;
getImgArray(res) {
var w = res.width;
var width = parseInt((res.width + 7) / 8 * 8 / 8);
var height = res.height;
let data = [29, 118, 48, 0];
// let data = [0x1d, 0x76, 0x30, 0];
data.push(parseInt(width % 256));
data.push(parseInt(width / 256));
data.push(parseInt(res.height % 256));
data.push(parseInt(res.height / 256));
var bits = new Uint8Array(height * width);
for (let y = 0; y < height; y++) {
for (let x = 0; x < w; x++) {
var color = res.data[(y * w + x) * 4 + 1];
if (color > 128) {
bits[parseInt(y * width + x / 8)] |= (0x80 >> (x % 8));
}
}
}
for (let i = 0; i < bits.length; i++) {
data.push((~bits[i]) & 0xFF)
}
return data;
}
// 获取写入数据需要的ids;
async getWriteIds(deviceId) {
return new Promise(async (resolve, reject) => {
let serviceIdData = await this.getServiceId(deviceId);
let services = serviceIdData.services;
// console.log(services);
let charId,
serviceId;
for (let i = 0; i < services.length; i++) {
const chars = await this.getCharId(deviceId, services[i].uuid);
// console.log(services[i].uuid, chars);
const charItem = chars.characteristics.filter(item => item.properties.write)[0];
if (charItem) {
charId = charItem.uuid;
serviceId = services[i].uuid;
// break;
// console.log(charId, serviceId);
}
}
resolve({ charId: charId, serviceId: serviceId })
})
}
isAndroid() {
return uni.getSystemInfoSync().osName === 'android'
}
}
export default BluetoothTools;
1、安卓设备有数据限制,如果传输数据过多。虽然writeBLE回调成功,但是会丢数据。可能会导致打印错乱、打印乱码。有些机型会直接不打印。
2、安卓设备必须要用到 writeMidware中的循环写入数据方式。将初始化,写入数据、打印空行这三个步骤分开。
所有的安卓设备都可能会在写入数据的时候,写入失败。一般过200ms后重新写入可能会成功,本方法中,是按照正常方式循环写入数据。一旦失败后会延迟100ms写入数据,不断尝试最多50次。
关于文中栅格位图涉及到的进制转换;
十六进制有两个字节组成,每个字节八位。每个字节有2的8次方=256种;256*256=65536;所以是从0-65535;而65536转为16进制是0x10000;所以这里两个字节的十六进制最大是0xffff;
高位字节0xff;低位字节也是0xff;
576转为十六进制是0x240;高位是0x02;低位是0x40;
576 % 256 = 64;//低位; Math.floor(576 / 256) = 2;//高位;
而64转为十六进制就是 0x40; 2转为十六进制0x02;
所以位图的长度表达方式为:L + H * 256 ; 64 + 2 * 256 = 576;文章来源:https://www.toymoban.com/news/detail-618649.html
打印txt文本
图片打印,数据太大容易导致打印乱码。而且ios报错机制不完善,采用node包"text-encoding-gbk”进行编码打印文字又快又不容易出错文章来源地址https://www.toymoban.com/news/detail-618649.html
// 字符串转buffer;
const Encoder = require('text-encoding-gbk');
textEncode(text = "") {
var uint8array = new Encoder.TextEncoder(
'gb18030', { NONSTANDARD_allowLegacyEncoding: true }).encode(text);
return uint8array;
}
//
到了这里,关于uni-app(android、ios) 使用蓝牙便携式打印机(热敏打印机)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!