项目需求,做了低耗蓝牙打印功能,总结下如何实现功能的。下面是需要的api,刚开始写的时候有点多绕来绕去还是有点头疼哈哈,这个有个很大的问题就是不支持蓝牙版本4.0以下的版本。需要兼容另找其他办法。对于版本低的就会出现一些莫名其妙的问题。
1.uni.openBluetoothAdapter 初始化蓝牙模块
2.uni.onBluetoothDeviceFound 监听寻找到新设备的事件
3.uni.getBluetoothAdapterState 获取本机蓝牙适配器状态
4.uni.stopBluetoothDevicesDiscovery 停止搜寻附近的蓝牙设备
5.uni.createBLEConnection 初始化蓝牙连接
6.uni.getBLEDeviceServices 获取蓝牙设备服务
7.uni.getBLEDeviceCharacteristics 获取蓝牙设备某个服务中所有特征值(characteristic)
8.uni.writeBLECharacteristicValue 向低功耗蓝牙设备特征值中写入二进制数据
9.uni.startBluetoothDevicesDiscovery 开始搜寻附近的蓝牙设备
首先需要初始化蓝牙模块,如果没有初始化后面都不会在执行。然后再搜索周围设备。再把搜索到的设备存入数组,方便渲染。
<view class="uni-list-cell uni-list-cell-pd auto-switch-cell">
<view class="uni-list-cell-db" @tap="switchBluetooth">蓝牙初始化</view>
</view>
<list class="bt-list" v-if="devData.length!=0">
<div v-for="(item,index) in devData" :key="index" class="bt-item">
<view class="singleLeft">
<div class="dev-single-name">
<div class="single">
<div :class="{'cgreen': item.RSSI >= 1}" style=" height: 8rpx;"> </div>
<div :class="{'cgreen': item.RSSI >= 2}" style=" height: 12rpx;"> </div>
<div :class="{'cgreen': item.RSSI >= 3}" style=" height: 15rpx;"> </div>
<div :class="{'cgreen': item.RSSI >= 4}" style=" height: 19rpx;"> </div>
<div :class="{'cgreen': item.RSSI >= 5}" style=" height: 22rpx;"> </div>
</div>
<div class="dev-name">
<div>{{item.name}}</div>
<div>{{item.deviceId}}</div>
</div>
</div>
</view>
<view class="singleRight">
<button plain type="primary" class="link-btn" @click="linkBLE(item,item.deviceId, item.name)" v-if="BTPrintStatus !== item.deviceId">选择</button>
<button plain type="primary" class="link-btn activelink" @click="closeBLE(item.deviceId)" v-if="BTPrintStatus === item.deviceId">取消</button>
</view>
</div>
</list>
// 打开蓝牙
function switchBluetooth(e) {
if (e.mp.detail) {
// 初始化蓝牙
uni.openBluetoothAdapter({
success: res => {
startSearchBTDev()
},
fail: err => {
showModal('提示', '请打开手机蓝牙')
}
})
} else {
// 断开蓝牙模块
uni.closeBluetoothAdapter({
success: res => {
console.log('关闭蓝牙模块成功', res)
},
fail: err => {
console.log('关闭蓝牙模块失败', err)
}
})
}
}
// 开始搜索蓝牙列表
function startSearchBTDev() {
this.devData = [] // 清空列表
onListenerBTDevFound(this)
store.commit('setAllBluthData', this.devData)//添加所有打印机数据
// 获取本机蓝牙适配器状态
getBTAdapterState().then(res => {
let available = res.available // 蓝牙适配器是否可用
let discovering = res.discovering // 是否处于搜索状态
if (res.available) {
if (res.discovering) {
this.stopDisBisBTDev()//停止搜寻设备
}
//搜索蓝牙
//开始搜寻附近的蓝牙外围设备
console.log("开始搜寻附近的蓝牙外围设备")
//开始搜寻附近的蓝牙外围设备。此操作比较耗费系统资源,请在搜索并连接到设备后调用
//uni.stopBluetoothDevicesDiscovery 方法停止搜索。
// 扫描附近设备
startDisBTDev().then(res => {
console.log('扫描成功', res)
// 开始监听新设备
}).catch(err => {
uni.showToast({ title: err })
})
} else {
console.log('本机蓝牙不可用')
}
}
}
// 获取本机蓝牙适配器状态
function getBTAdapterState (fn) {
return new Promise((resolve, reject) => {
uni.getBluetoothAdapterState({
success: res => {
resolve(res)
},
fail: err => {
console.log('获取适配器状态失败' + err)
reject(BTStatus[err.errCode])
}
})
})
}
//监听寻找到新设备的事件
function onListenerBTDevFound (that) {
uni.onBluetoothDeviceFound(res => {
// console.log('监听到的设备', res)
let dev = res.devices[0]
// name和localName都存在
if (dev.name && dev.localName) {
let arr = that.devData.filter(item => item.deviceId === dev.deviceId)
if (arr.length > 0) {
// 说明存在相同设备,要进行RSSI更新
let n = that.devData.indexOf(arr[0])
// 转换信号
let rssi = Math.floor(max(0, dev.RSSI + 100) / 10)
if (rssi <= 0) {
// 无信号删除
that.devData.splice(n, 1)
} else {
// 有信号更新
that.devData[n].RSSI = rssi
}
} else {
// 新设备,直接push
that.devData.push(dev)
}
}
that.devData.forEach((currentValue, index,)=>{
that.devData[index].services=[]
})
})
}
// 开始搜寻附近的蓝牙设备
// 注意,该操作比较耗费系统资源,请在搜索并连接到设备后调用 stop 方法停止搜索。
function startDisBTDev () {
return new Promise((resolve, reject) => {
uni.startBluetoothDevicesDiscovery({
// services: ['FEE7'] uuid列表
// interval: 500, // 间隔500毫秒
allowDuplicatesKey: true, // 允许重复上报, RSSI不同
success: res => {
resolve(res)
},
fail: err => {
console.log('开始搜索失败' + err)
reject(BTStatus[err.errCode])
}
})
})
}
蓝牙连接的时间有时效性,2min中过后会自动断开,然后导致打印数据输入不了,所以我在需要打印的时候再进行对设备连接,这样做可以在其他页面上也可以使用打印功能,并成功保持连接。
<div style="width: 200rpx; background: #4EB331; color: white; border-radius: 8rpx;padding: 20rpx 0;text-align: center; margin: 10rpx auto; font-size: 26rpx;" v-show="deviceName" @click="testPrint">
测试蓝牙打印
</div>
let tmpOrder = ''
let tmpSkus = ''
async function testPrint() {
let order = {
orderSeq: '测试打印',
buyerComment: '测试打印'
}
let orderSkus = []
try {
store.commit('Loading', true)
let { data } = await data
order = data[0]
console.log('测试打印testPrint-order', order)
orderSkus = await orderSkus
console.log('测试打印testPrint-orderSkus', orderSkus)
} catch (e) {
console.log(e)
} finally {
store.commit('Loading', false)
}
// 请求订单
// 请求sku
startPrint(order, orderSkus, () => {
console.log('-----------打印完成-----------')
uni.showToast({ title: '打印完成' })
}, err => {
console.log('-----------打印完成-----------')
uni.showToast({ title: err })
})
}
// 初始化蓝牙准备打印
export function startPrint(order, skus, fn, errFn) {
tmpOrder = order
tmpSkus = skus
console.log('tmp', tmpOrder, tmpSkus)
let _deviceId = uni.getStorageSync('deviceId')
console.log('deviceId', _deviceId)
createBLE(_deviceId, fn, errFn)
// printFormat(fn, errFn)
}
//初始化蓝牙连接
function createBLE(_deviceId, fn, errFn) {
uni.createBLEConnection({
deviceId: _deviceId,
success(res) {
console.log('createBLEConnection', res)
if (res.errMsg == "createBLEConnection:ok") {
//这个地方使用了setTimeout等待一秒种再去获取,直接获取我们可能出现获取不到的情况
setTimeout(() => {
console.log('to----getBLEServices')
getBLEServices(_deviceId, fn, errFn)//获取蓝牙设备服务
}, 2000)
} else {
console.log(res, '获取蓝牙设备服务失败')
}
},
fail(res) {
console.log('fail', res)
}
})
}
//获取蓝牙设备服务
function getBLEServices(_deviceId, fn, errFn) {
let deviceId = _deviceId
console.log("获取蓝牙设备所有服务(service)。---------------", _deviceId)
uni.getBLEDeviceServices({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId,
success(res) {
console.log('getBLEDeviceServices--res', res)
let serviceId = ""
console.log('res.services.length', res.services)
//有的低版本设备连接会出错,但是重新连接就可以链接上,所以在这里先断开连接再重新连接该设备
if (res.services.length == 0) {
console.log('res.services.length == 0')
uni.closeBLEConnection({
deviceId,
success(res) {
console.log('断开与设备的连接', res)
//重新连接
setTimeout(() => {
createBLE(deviceId, fn, errFn)
}, 1000)
}
})
return
}
for (var s = 0; s < res.services.length; s++) {
console.log(res.services[s].uuid)
let serviceId = res.services[s].uuid
uni.getBLEDeviceCharacteristics({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: serviceId,
success(res) {
var re = JSON.parse(JSON.stringify(res))
console.log('re.characteristics.length', re.characteristics.length)
for (var c = 0; c < re.characteristics.length; c++) {
//这里的characteristics有多个值,且write值不一样,所以要循环判定
if (re.characteristics[c].properties.write == true) {
let uuid = re.characteristics[c].uuid
//进行ID匹配
let arr2222 = store.state.allBluthData
console.log('store.state.allBluthData', arr2222)
for (var index in arr2222) {
if (arr2222[index].deviceId == deviceId) {
console.log('arr2222[index].services', arr2222[index].services)
arr2222[index].services.push({
serviceId: serviceId,
characteristicId: uuid
})
console.log('arr2222//进行ID匹配', arr2222)
break
}
}
let arr = arr2222.filter((currentValue, index) => {
return arr2222[index].deviceId === _deviceId
})
store.commit('commitBTDevCharact', arr)
console.log('store.state.BTDevCharact', store.state.BTDevCharact)
console.log('与对应设备建立链接', arr2222)
}
}
printFormat(fn, errFn)
},
fail(res) {
console.log('getBLEDeviceCharacteristics', res)
}
})
}
},
fail(res) {
console.log(res)
},
})
}
async function printFormat(fn, errFn) {
try {
console.log('推送打印数据')
toast('开始打印')
pos.ClearQueue()
// 京西菜市
pos.PrintBigTitle('京西菜市')
pos.PrintJumpLines(1)
pos.PrintMiddleText('手机买菜上京西')
pos.PrintMiddleText('极速到家送惊喜')
pos.PrintMiddleText('--------------------------------')
pos.PrintJumpLines(1)
// 下单时间
pos.PrintText(`下单时间:${timeFormat2(tmpOrder.orderCreatedAt)}`)
// 预计送达
pos.PrintText(`预计送达:${expectTime()}`)
// 订单号
pos.PrintText(`订单编号:${tmpOrder.vendorOrderID}`)
pos.PrintJumpLines(1)
// #1
pos.PrintBigLeftTitle(`${vendorID2()}#${tmpOrder.orderSeq + 1}`)
pos.PrintJumpLines(1)
pos.PrintBarcode(`${tmpOrder.vendorOrderID}`)
if (tmpOrder.vendorID === 3) pos.PrintBigLeftTitle(`饿百取货码: ${tmpOrder.vendorOrderID2.slice(tmpOrder.vendorOrderID2.length - 4)}`)
pos.PrintJumpLines(1)
// 客户信息
pos.PrintText(`客户: ${tmpOrder.consigneeName}`)
pos.PrintText(`电话: ${tmpOrder.consigneeMobile}`)
pos.PrintText(`地址: ${tmpOrder.consigneeAddress}`)
pos.PrintJumpLines(1)
// 客户备注
pos.PrintText(`客户备注:`)
pos.PrintBigLeftTitle(`${tmpOrder.buyerComment}`)
pos.PrintJumpLines(1)
// 商品明细
pos.PrintText(`商品明细:`)
pos.PrintText(`品名 数量 `)
pos.PrintMiddleText('--------------------------------')
tmpSkus.forEach(item => {
let comparePrice = item.salePrice / 100
pos.PrintText(`${item.skuName}`)
pos.PPP(`x${item.count}`, ` `, ` `)
})
pos.PrintJumpLines(1)
// 总计
pos.PrintNameValue(`共${tmpOrder.skuCount}种${tmpOrder.goodsCount}件商品`, '')
pos.PrintJumpLines(1)
pos.PrintMiddleText('--------------------------------')
// 质量问题联系
pos.PrintTitle('商品质量问题,请联系:')
pos.PrintJumpLines(1)
pos.PrintTitle(`${store.state.storeInfo.name}:${store.state.storeInfo.tel1}`)
pos.PrintJumpLines(1)
pos.PrintText('更多信息请关注官方微信: 京西菜市')
pos.PrintJumpLines(1)
pos.PrintQRcode2('http://weixin.qq.com/r/tkkDGzTERmk5rXB49xyk')
pos.PrintJumpLines(2)
pos.PrintMiddleText('--------------------------------')
pos.PrintMiddleText('--------------------------------')
pos.PrintJumpLines(2)
// 打印完成回调
console.log('打印即将完成')
pos.DaYin(async () => {
uni.closeBluetoothAdapter({
success: res => {
console.log('断开蓝牙适配器成功', res)
},
fail: err => {
console.log('断开蓝牙适配器失败', err)
},
complete: async () => {
state.commit('setblueToothCheckedglobal')
console.log('完成了')
toast('打印完成')
tmpOrder = ''
tmpSkus = ''
fn && fn()
}
})
})
} catch (e) {
errFn && errFn()
}
}
function DaYin(fn) {
if (isPrint) return;
DaYin2(fn);
}
var isPrint = false;
function DaYin2(fn) {
//打印的时候不能把数据全部打印,要分开打印,不然会报错
if (QueueWrite.length > 0) {
isPrint = true;
var shuzu = QueueWrite.shift();
lanyaposgo(shuzu, fn);
} else {
isPrint = false;
fn && fn()
}
}
function lanyaposgo(text, fn) {
uni.writeBLECharacteristicValue({
deviceId: store.state.BTDevCharact[0].deviceId,
serviceId: store.state.BTDevCharact[0].services[0].serviceId,
characteristicId: store.state.BTDevCharact[0].services[0].characteristicId,
value: text.buffer,
success: function (res) {
DaYin2(fn);
},
fail: function (res) {
isPrint = false;
console.log("打印错误:", res);
console.log("打印数据:", "/" + text + "/");
}
});
}
最后是效果图以及打印输出的结果