uni-app(android、ios) 使用蓝牙便携式打印机(热敏打印机)
机型等参数
- 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.lengthif (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;
waitting complete。。。
uni-app(android、ios) 使用蓝牙便携式打印机(热敏打印机)相关推荐
- 微信内置浏览器无法下载app(Android/ios)软件 微信内下载链接打不开的解决方法
很多朋友的APP推广链接需要在微信中进行的网页宣传.传播.下载等等,但是各位朋友一定发现了微信中是屏蔽掉了APP的下载链接的.但是微信最为一个最大的社交平台,为了自身的利益,屏蔽掉了所有APK的下载链 ...
- Adobe源码泄漏?3行代码搞定,Flash动画无缝导入Android/iOS/cocos2dx(一)
[注] iOS代码已重构,效率提升90%,200层动画不卡.[2016.10.27] 项目介绍 项目名称:FlashAnimationToMobile 源码. 使用方法点这里. 这是一个把flash中 ...
- 关于便携式打印机程序开发(一、原生安卓蓝牙调用)
关于便携式打印机程序开发(一.原生安卓蓝牙调用) 综述 软硬件 SDK集成到项目 CPCL协议开发 综述 使用android程序,调用蓝牙,和打印机配对之后,可以连接打印机,通过(WIFI.蓝牙.US ...
- android 退出app代码_uniapp退出APP应用(IOS+安卓)
前言: 近几日使用uni-app 开发移动应用APP,在用户首次安装的时候,需要仔细阅读隐私政策后,方可继续使用APP,否则就直接退出APP 废话结束, 正式进入开发 前置条件: 开发环境:windo ...
- 省钱兄校园跑腿源码(公众号+APP+小程序+Android+IOS)校园跑腿社区跑腿同城跑腿任务兼职小程序uniapp前端模版
开源代码是用户端uniapp部分源码,使用hbuilder导入即可运行 只提供参考学习使用!已经获得软著!不可商业使用!感谢支持 h5体验地址 h5:https://paotui.xianmxkj.c ...
- android蓝牙通信_Flutter通过BasicMessageChannel实现Flutter 与Android iOS 的双向通信
题记: --不到最后时刻,千万别轻言放弃,无论结局成功与否,只要你拼博过,尽力过,一切问心无愧. 通过 Flutter 来进行移动应用开发,打包 Android .iOS 双平台应用程序,在调用如相机 ...
- 前端MUI+H5+HBuilderX开发APP(IOS,android),后台Springboot,java学习与实践文章,更新中(二)
前端MUI+H5+HBuilderX开发APP(IOS,android),后台Springboot,项目搭建,图标设置等,更新中(二) 新建我的第一个APP manifest.json: 图标设置: ...
- 便携式打印机连接蓝牙方式
蓝牙接口是支持设备短距离通信(一般是10m之内)的无线电技术,能在移动电话.PDA.无线耳机.笔记本电脑.相关外设等众多设备之间进行无线信息交换,蓝牙的标准是IEEE802.15,工作在2.4GHz频 ...
- 蓝牙聊天App设计1:Android Studio制作蓝牙聊天通讯软件(UI界面设计)
前言:蓝牙聊天App设计全部有三篇文章(一.UI界面设计,二.蓝牙搜索配对连接实现,三.蓝牙连接聊天),这篇文章是一.UI界面设计 课程1:Android Studio小白安装教程,以及第一个Andr ...
最新文章
- T-Mobile旗下网站又曝安全漏洞 允许任何人查看他人账户信息
- bzoj1492: [NOI2007]货币兑换Cash
- MediatR-进程内的消息通信框架
- Python自动化运维开发----基础(八)字符串
- Linux下Nodejs安装
- java 日期相减得分钟_java日期相减得到分钟??????
- Comparable接口
- linux面试命令问题,面试常见的Linux命令及问题整理
- java实现redis批量lpush,redis lpush list命令
- DDNS设置(自用)
- SuperMap iDesktop常见问题解答集锦(八)
- 2012百度移动开发者大会汇报
- 【jQuery进阶】子菜单插件Slight Submenu
- 身份证的行政区划代码
- 苹果App卡审原因猜测分析
- Excel 删除数据temp 恢复
- 为什么我要选择使用 Yarn 来做 Docker 的调度引擎
- C++计算机视觉库OpenCV在Visual Studio 2022的配置方法
- Python必学的4个实战项目,拿走不谢
- log4j.properties详解
热门文章
- 微信公众号实现路径规划和导航功能
- unity 3d中使用BMFont制作清晰字体
- 影响拼多多店铺流量的五大指标你知道多少?
- 古人为啥说“男不养猫 女不养狗”
- kubernetes_22_基于containerd部署kubernetes v1.20.5
- Java设计模式 --- 七大常用设计模式示例归纳
- 音视频同步录音监控的需求水涨船高
- 什么样的内容能够使读者产生共鸣?可以从这几个方面来看
- linux内核保留内存,Linux内核开机保留大块内存的方法
- Check Point防火墙开启远程管理服务