news 2026/9/18 17:30:28

微信小程序蓝牙连接小票打印机:从扫描到打印的完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信小程序蓝牙连接小票打印机:从扫描到打印的完整实战指南

简介:这份PDF文档面向微信小程序开发者,尤其是需要实现蓝牙外设通信、对接小票打印机的初中级工程师。内容围绕微信小程序蓝牙API展开,系统讲解从开启蓝牙适配器、搜索附近设备、建立BLE连接,到获取服务ID与特征值、写入打印数据、设置通知接收反馈的完整链路,并强调异步回调处理与加载提示等体验细节。资源包共1个PDF文件,约44KB,篇幅精炼,适合作为手边速查与代码对照材料。目前已有2855人学习下载,说明其在同类蓝牙打印场景中具备一定参考价值。读者可从中获得可直接借鉴的实例代码片段、关键API调用顺序、特征值筛选逻辑以及连接失败与找不到读写特征值时的排错思路,帮助快速搭建可运行的小程序蓝牙打印原型,减少自行摸索成本。

1. 从一次收银台卡纸说起:微信小程序蓝牙连接小票打印机的真实门槛

很多做零售、餐饮 SaaS 的团队都会遇到这个需求:收银员在微信小程序里点“打印小票”,旁边那台 58mm 热敏小票打印机就开始吐纸。听起来简单,但真正动手时,第一道坎往往不是打印指令,而是蓝牙连接本身——iOS 和 Android 的差异、设备 ID 每次扫描都变、连接后写特征值失败、中文乱码、切后台断连,这些问题会一个接一个冒出来。

微信小程序提供了wx.openBluetoothAdapterwx.startBluetoothDevicesDiscoverywx.createBLEConnection这一整套低功耗蓝牙(BLE)API,理论上可以完成从扫描到写入的全部流程。但小票打印机大多是经典蓝牙(SPP)或 BLE 双模设备,小程序只能走 BLE 通道,所以选型阶段就要确认打印机支持 BLE 透传。这篇文章面向已经会写小程序页面、但对蓝牙通信不熟的开发者,把扫描、连接、服务发现、指令拼装、异常重连这条链路拆开讲清楚,代码可以直接抄进项目里改。

2. 微信小程序 BLE 连接小票打印机的完整链路与关键 API

2.1 为什么小票打印机必须走 BLE 而不是经典蓝牙

微信小程序的蓝牙能力只开放了低功耗蓝牙(BLE)接口,没有暴露经典蓝牙 SPP 的 socket 通道。市面上常见的 58mm 小票打印机,如果只支持经典蓝牙,在小程序里是搜不到、连不上的。所以采购或对接前,一定要确认设备支持 BLE 4.0 及以上,并且厂商提供了 BLE 透传的写特征值(write characteristic)。

常见做法是:先用手机系统蓝牙或厂商 App 确认设备能被 BLE 扫描工具发现,再拿到它的deviceIdserviceIdcharacteristicId三个关键标识。这三个值在小程序里分别对应wx.createBLEConnectionwx.getBLEDeviceServiceswx.getBLEDeviceCharacteristics的返回结果。

注意:iOS 上deviceId是系统分配的 UUID,同一台打印机在不同手机上不一样;Android 上通常是 MAC 地址。不要把deviceId硬编码进代码,每次使用前重新扫描获取。

2.2 扫描、连接、服务发现的最小可用代码

下面这段代码把“初始化适配器 → 开始扫描 → 连接 → 拿服务 → 拿特征值”串成一条链,每一步都做了错误分支。

// pages/printer/printer.js Page({ data: { deviceId: '', serviceId: '', characteristicId: '' }, // 1. 初始化蓝牙适配器 initBluetooth() { wx.openBluetoothAdapter({ success: () => { this.startDiscovery(); }, fail: (err) => { // 10001 表示蓝牙未开启 console.error('适配器初始化失败', err); wx.showToast({ title: '请打开手机蓝牙', icon: 'none' }); } }); }, // 2. 开始扫描,按名称过滤目标打印机 startDiscovery() { wx.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, success: () => { wx.onBluetoothDeviceFound((res) => { const device = res.devices.find(d => d.name && d.name.includes('Printer')); if (device) { this.setData({ deviceId: device.deviceId }); wx.stopBluetoothDevicesDiscovery(); // 找到即停,省电 this.connectDevice(device.deviceId); } }); } }); }, // 3. 建立连接 connectDevice(deviceId) { wx.createBLEConnection({ deviceId, timeout: 10000, success: () => { this.getServices(deviceId); }, fail: (err) => { console.error('连接失败', err); } }); }, // 4. 获取服务列表,找到写特征值 getServices(deviceId) { wx.getBLEDeviceServices({ deviceId, success: (res) => { // 常见打印机主服务 UUID 以 FF00 或 E7810A71 开头 const target = res.services.find(s => s.uuid.toUpperCase().includes('FF00')) || res.services[0]; this.setData({ serviceId: target.uuid }); this.getCharacteristics(deviceId, target.uuid); } }); }, // 5. 获取特征值,筛选支持 write 的那个 getCharacteristics(deviceId, serviceId) { wx.getBLEDeviceCharacteristics({ deviceId, serviceId, success: (res) => { const writeChar = res.characteristics.find(c => c.properties.write); if (writeChar) { this.setData({ characteristicId: writeChar.uuid }); } } }); } });

逻辑说明:openBluetoothAdapter是所有 BLE 操作的前置条件,失败最常见的原因是用户没开蓝牙或系统未授权。startBluetoothDevicesDiscoveryallowDuplicatesKey: false可以避免同一设备重复上报,减少回调压力。找到目标设备后立刻stopBluetoothDevicesDiscovery,因为扫描和连接同时进行会显著降低连接成功率,这是很多人第一次做蓝牙时踩的坑。

参数说明:createBLEConnectiontimeout单位是毫秒,默认值偏短,建议设到 10000。getBLEDeviceServices返回的services数组里,主服务通常排在最前,但不同厂商 UUID 不同,稳妥做法是按已知前缀匹配,匹配不到再取第一个。

2.3 连接参数与常见失败码对照

失败场景典型 errCode处理方式
蓝牙未开启10001引导用户打开系统蓝牙
设备未找到10002检查打印机是否已配对占用
连接超时10003先 stopDiscovery 再重连
服务未发现10004延迟 500ms 再调 getBLEDeviceServices
特征值不支持写10005换一个 characteristic 或确认设备型号

这张表建议直接放进项目的错误处理函数里,按 errCode 给出不同的用户提示,而不是统一弹“连接失败”。

3. 小票打印指令拼装:从文本到 ESC/POS 字节流

3.1 ESC/POS 指令集在小程序里的字节处理

小票打印机普遍兼容 ESC/POS 指令集,核心是把文本和格式控制符转成ArrayBuffer,再通过wx.writeBLECharacteristicValue写入。小程序里没有 Node 的 Buffer,需要用Uint8Array手动拼。

// utils/escpos.js // 将字符串按 GBK 编码转成字节数组(打印机通常用 GBK) function strToGBKBytes(str) { // 小程序无内置 GBK 编码,需引入 encoding 库或使用厂商提供的映射表 // 这里以 UTF-8 演示结构,实际项目替换为 GBK 编码函数 const utf8 = unescape(encodeURIComponent(str)); const bytes = []; for (let i = 0; i < utf8.length; i++) { bytes.push(utf8.charCodeAt(i)); } return bytes; } // 拼装一张小票的完整指令 function buildReceipt(lines) { const cmd = []; // 初始化打印机 cmd.push(0x1B, 0x40); // 居中 + 放大字体打印标题 cmd.push(0x1B, 0x61, 0x01); cmd.push(0x1D, 0x21, 0x11); cmd.push(...strToGBKBytes('销售小票\n')); // 恢复默认字体,左对齐 cmd.push(0x1D, 0x21, 0x00); cmd.push(0x1B, 0x61, 0x00); // 逐行打印内容 lines.forEach(line => { cmd.push(...strToGBKBytes(line + '\n')); }); // 走纸 3 行并切纸 cmd.push(0x1B, 0x64, 0x03); cmd.push(0x1D, 0x56, 0x42, 0x00); return new Uint8Array(cmd).buffer; } module.exports = { buildReceipt };

逻辑说明:0x1B 0x40是初始化指令,每次打印前必须发,否则上一次的格式会残留。0x1B 0x61控制对齐,参数0x00左对齐、0x01居中。0x1D 0x21控制字体大小,0x11表示宽高各放大一倍。切纸指令0x1D 0x56 0x42 0x00是全切,部分机型用0x1D 0x56 0x41 0x00半切。

参数说明:中文必须用 GBK 编码,UTF-8 直接写会乱码。小程序本身没有 GBK 编码函数,常见做法是引入一个轻量的编码转换库,或者让后端把文本转成 GBK 字节数组的 base64 再下发,前端只负责拼指令头尾。

3.2 分包写入与写入节奏控制

BLE 单次写入有长度限制,iOS 通常 20 字节,Android 可协商到 512 字节。一张小票动辄几百字节,必须分包。

// 分包写入,每包 20 字节,间隔 20ms function writeInChunks(deviceId, serviceId, characteristicId, buffer) { const chunkSize = 20; const data = new Uint8Array(buffer); let offset = 0; function writeNext() { if (offset >= data.length) return; const chunk = data.slice(offset, offset + chunkSize); wx.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: chunk.buffer, success: () => { offset += chunkSize; setTimeout(writeNext, 20); // 控制节奏,避免丢包 }, fail: (err) => { console.error('写入失败', err); } }); } writeNext(); }

逻辑说明:setTimeout的 20ms 间隔是经验值,太快会导致打印机缓冲区溢出丢指令,太慢则打印明显卡顿。如果打印机支持 MTU 协商,可以在连接后调用wx.setBLEMTU把单包提到 200 字节以上,减少分包次数。

注意:wx.setBLEMTU只在 Android 上有效,iOS 由系统自动协商,不要依赖它做跨平台统一。

4. 真机调试与断连重连的实战排错

4.1 iOS 与 Android 的连接差异排查

iOS 上deviceId是 UUID,每次扫描可能不同,且系统会缓存已配对设备,导致onBluetoothDeviceFound有时不回调。解决办法是监听wx.onBluetoothAdapterStateChange,在适配器可用后延迟 300ms 再开始扫描。Android 上deviceId是 MAC 地址,相对稳定,但部分机型需要先申请定位权限才能扫描到 BLE 设备。

真机调试时,开发者工具里的蓝牙模拟基本不可用,必须用真机。常见现象是:开发者工具能连上,真机连不上——这通常是权限或系统蓝牙缓存问题。处理步骤是:关闭手机蓝牙再打开、删除系统里该打印机的配对记录、重启小程序。

4.2 监听连接状态并实现自动重连

BLE 连接在切后台、信号干扰、打印机休眠时都会断开,必须监听onBLEConnectionStateChange

// 在连接成功后注册监听 wx.onBLEConnectionStateChange((res) => { if (!res.connected) { console.warn('连接已断开', res.deviceId); // 延迟 1s 重连,避免频繁重试 setTimeout(() => { this.connectDevice(res.deviceId); }, 1000); } });

逻辑说明:重连前要确保deviceId仍然有效,如果设备已关机,重连会一直失败,需要加一个最大重试次数(比如 3 次),超过后提示用户检查打印机电源。另外,重连成功后必须重新调用getBLEDeviceServicesgetBLEDeviceCharacteristics,因为服务句柄可能已经变化。

参数说明:onBLEConnectionStateChange是全局监听,注册一次即可,不要在每个页面重复注册,否则会触发多次回调。建议在app.jsonLaunch里统一管理蓝牙状态。

4.3 打印内容乱码与切纸异常的定位方法

乱码九成是编码问题:确认打印机支持的编码(GBK 居多),确认发送的字节数组没有被二次转码。切纸异常通常是切纸指令不被机型支持,可以先用厂商提供的测试指令集逐条验证。定位方法很简单:先只发初始化指令和一行英文,确认能打;再加中文,确认编码;最后加切纸,确认指令。每加一步就真机验证一次,比一次性拼完整张小票再排查要快得多。

5. 把打印能力封装成可复用模块的进阶技巧

5.1 用 Promise 封装蓝牙链路,避免回调地狱

上面的代码是回调风格,实际项目里建议封装成 Promise,页面里用async/await调用。

// utils/ble.js function openAdapter() { return new Promise((resolve, reject) => { wx.openBluetoothAdapter({ success: resolve, fail: reject }); }); } function connect(deviceId) { return new Promise((resolve, reject) => { wx.createBLEConnection({ deviceId, timeout: 10000, success: resolve, fail: reject }); }); } // 页面里使用 async printReceipt() { try { await openAdapter(); await connect(this.data.deviceId); const buffer = buildReceipt(this.data.lines); await writeInChunks(this.data.deviceId, this.data.serviceId, this.data.characteristicId, buffer); wx.showToast({ title: '打印成功' }); } catch (e) { wx.showToast({ title: '打印失败', icon: 'none' }); } }

逻辑说明:Promise 封装后,每个蓝牙步骤的失败都能被try/catch统一捕获,页面逻辑更清晰。注意writeInChunks也要改成返回 Promise,在最后一包写完时 resolve。

5.2 打印队列与并发控制

收银场景可能连续点多次打印,如果并发写入,BLE 通道会乱序。做法是维护一个打印队列,前一张打完再打下一张。

队列状态处理动作
空闲立即执行打印
打印中新任务入队,等待
队列长度 > 5拒绝新任务,提示稍后

队列用数组实现,每次打印完成后shift出下一个任务。这个机制能有效避免“点了打印没反应”或“打出半张”的问题。

5.3 一个容易被忽略的细节:打印完成后延迟断开

打印指令写入成功不代表打印机已经打完,尤其是走纸和切纸需要时间。如果写完立刻断开连接,最后几包指令可能还没被打印机处理。稳妥做法是在写入完成后延迟 500ms 到 1s 再断开,或者监听打印机的状态特征值(部分机型支持)确认打印完成。这个延迟值可以根据机型调整,58mm 打印机一般 500ms 足够,80mm 打印机建议 800ms 以上。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 17:30:12

MySQL 四大连接与笛卡尔乘积:从数据爆炸到执行计划

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 17:28:50

超分辨率与共聚焦显微镜技术对比与应用指南

1. 显微镜技术发展现状与核心需求现代显微成像技术已经发展出多种高分辨率成像方案&#xff0c;其中超分辨率显微镜和共聚焦显微镜是两种最具代表性的技术路线。作为一名在生物医学成像领域工作多年的技术人员&#xff0c;我经常需要向不同背景的研究者解释这两种技术的本质差异…

作者头像 李华
网站建设 2026/9/18 17:25:53

4K/60fps摇滚现场制作全链路解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华