微信小程序蓝牙连接打印机打印单据完整Demo【蓝牙小票打印】

文章目录

  • 一、准备工作
    • 1. 硬件准备
    • 2. 开发环境
  • 二、小程序配置
    • 1. 修改app.json
  • 三、完整代码实现
    • 1. pages/index/index.wxml
    • 2. pages/index/index.wxss
    • 3. pages/index/index.js
  • 四、ESC/POS指令说明
  • 五、测试流程
  • 六、常见问题解决
  • 七、进一步优化建议


在这里插入图片描述

下面我将提供一个完整的微信小程序通过蓝牙连接热敏打印机(支持ESC/POS指令)打印订单小票的详细实现方案。本教程假设您使用的是常见的蓝牙热敏打印机(如佳博、芯烨等品牌)。

一、准备工作

1. 硬件准备

  • 蓝牙热敏小票打印机(支持 ESC/POS 指令)
  • 确保打印机已充电/通电
  • 知道打印机的蓝牙名称(如"GBTP-XXXX")

2. 开发环境

  • 微信开发者工具
  • 已注册微信小程序账号
  • 小程序已开通蓝牙权限(在 app.json 中配置)

二、小程序配置

1. 修改app.json

{"pages": ["pages/index/index"],"permission": {"scope.userLocation": {"desc": "你的位置信息将用于蓝牙设备搜索"}},"requiredPrivateInfos": ["getLocation", "chooseAddress", "startBluetoothDevicesDiscovery"]
}

三、完整代码实现

1. pages/index/index.wxml

<view class="container"><button bindtap="searchPrinter">搜索蓝牙打印机</button><view wx:for="{{devices}}" wx:key="deviceId"><view class="device-item" bindtap="connectPrinter" data-device="{{item}}">{{item.name}} ({{item.deviceId}})</view></view><button bindtap="printTest" disabled="{{!connected}}">打印测试小票</button><button bindtap="printOrder" disabled="{{!connected}}">打印订单</button><view class="status">状态: {{status}}</view>
</view>

2. pages/index/index.wxss

.container {padding: 20px;
}.device-item {padding: 15px;border-bottom: 1px solid #eee;
}button {margin-top: 15px;
}.status {margin-top: 20px;color: #666;font-size: 14px;
}

3. pages/index/index.js

Page({data: {devices: [],         // 搜索到的设备列表connected: false,    // 是否已连接deviceId: '',        // 当前连接的设备IDserviceId: '',       // 蓝牙服务UUIDcharacteristicId: '', // 蓝牙特征值UUIDstatus: '未连接',     // 状态提示orderInfo: {         // 示例订单数据orderNo: '20231115001',date: '2023-11-15 14:30',items: [{name: '有机青菜', price: 12.5, quantity: 2},{name: '新鲜番茄', price: 8.0, quantity: 3},{name: '优质土豆', price: 5.5, quantity: 1}],total: 47.5,address: '北京市朝阳区科技园A座1001室',phone: '138****1234'}},// 搜索蓝牙设备searchPrinter() {this.setData({status: '正在搜索蓝牙设备...'});// 初始化蓝牙模块wx.openBluetoothAdapter({success: (res) => {console.log('蓝牙适配器初始化成功', res);this.startDiscovery();},fail: (err) => {console.error('蓝牙适配器初始化失败', err);this.setData({status: '蓝牙初始化失败,请检查手机蓝牙是否开启'});}});},// 开始搜索设备startDiscovery() {wx.startBluetoothDevicesDiscovery({allowDuplicatesKey: false,success: (res) => {console.log('开始搜索设备', res);this.setData({status: '正在搜索设备...'});// 监听找到新设备事件wx.onBluetoothDeviceFound((res) => {const devices = res.devices.filter(device => device.name && device.name.indexOf('GBTP') !== -1);if (devices.length > 0) {this.setData({devices: devices,status: `找到${devices.length}个打印机设备`});wx.stopBluetoothDevicesDiscovery(); // 找到设备后停止搜索}});},fail: (err) => {console.error('搜索设备失败', err);this.setData({status: '搜索设备失败'});}});},// 连接打印机connectPrinter(e) {const device = e.currentTarget.dataset.device;this.setData({status: `正在连接${device.name}...`});// 连接设备wx.createBLEConnection({deviceId: device.deviceId,success: (res) => {console.log('设备连接成功', res);this.setData({deviceId: device.deviceId,status: `已连接${device.name}`});this.getBLEDeviceServices(device.deviceId);},fail: (err) => {console.error('设备连接失败', err);this.setData({status: '连接失败'});}});},// 获取蓝牙服务getBLEDeviceServices(deviceId) {wx.getBLEDeviceServices({deviceId: deviceId,success: (res) => {console.log('获取服务成功', res.services);for (const service of res.services) {// 通常蓝牙打印机的服务UUID是FF00或FFE0if (service.uuid.startsWith('FFE0') || service.uuid.startsWith('FF00')) {this.setData({serviceId: service.uuid});this.getBLEDeviceCharacteristics(deviceId, service.uuid);break;}}},fail: (err) => {console.error('获取服务失败', err);}});},// 获取蓝牙特征值getBLEDeviceCharacteristics(deviceId, serviceId) {wx.getBLEDeviceCharacteristics({deviceId: deviceId,serviceId: serviceId,success: (res) => {console.log('获取特征值成功', res.characteristics);for (const characteristic of res.characteristics) {// 寻找可写的特征值if (characteristic.properties.write) {this.setData({characteristicId: characteristic.uuid,connected: true});console.log('打印机已准备好');break;}}},fail: (err) => {console.error('获取特征值失败', err);}});},// 打印测试小票printTest() {this.setData({status: '正在打印测试小票...'});// ESC/POS指令const buffer = new ArrayBuffer(100);const dataView = new Uint8Array(buffer);// 初始化打印机dataView[0] = 0x1B;dataView[1] = 0x40;// 设置居中dataView[2] = 0x1B;dataView[3] = 0x61;dataView[4] = 0x01;// 设置字体大小dataView[5] = 0x1D;dataView[6] = 0x21;dataView[7] = 0x11;// 打印文本const text = "测试小票\n";for (let i = 0; i < text.length; i++) {dataView[8 + i] = text.charCodeAt(i);}// 换行const lineBreak = "\n\n\n\n";for (let i = 0; i < lineBreak.length; i++) {dataView[8 + text.length + i] = lineBreak.charCodeAt(i);}// 切纸dataView[8 + text.length + lineBreak.length] = 0x1D;dataView[9 + text.length + lineBreak.length] = 0x56;dataView[10 + text.length + lineBreak.length] = 0x42;dataView[11 + text.length + lineBreak.length] = 0x00;// 发送数据this.writeBLECharacteristicValue(buffer);},// 打印订单printOrder() {this.setData({status: '正在打印订单...'});// 创建缓冲区const commands = [];// 初始化打印机commands.push(0x1B, 0x40);// 设置居中commands.push(0x1B, 0x61, 0x01);// 设置大字体commands.push(0x1D, 0x21, 0x11);// 打印标题this.addTextToCommand(commands, "蔬菜配送订单\n");// 恢复默认字体commands.push(0x1D, 0x21, 0x00);// 订单信息this.addTextToCommand(commands, `订单号: ${this.data.orderInfo.orderNo}\n`);this.addTextToCommand(commands, `日期: ${this.data.orderInfo.date}\n\n`);// 商品列表this.addTextToCommand(commands, "----------------------------\n");this.addTextToCommand(commands, "商品名称   单价  数量  小计\n");this.addTextToCommand(commands, "----------------------------\n");this.data.orderInfo.items.forEach(item => {const line = `${item.name}    ${item.price}   ${item.quantity}   ${(item.price * item.quantity).toFixed(2)}\n`;this.addTextToCommand(commands, line);});this.addTextToCommand(commands, "----------------------------\n");this.addTextToCommand(commands, `总计: ¥${this.data.orderInfo.total}\n\n`);// 配送信息this.addTextToCommand(commands, `配送地址: ${this.data.orderInfo.address}\n`);this.addTextToCommand(commands, `联系电话: ${this.data.orderInfo.phone}\n\n`);// 感谢语commands.push(0x1B, 0x61, 0x01); // 居中this.addTextToCommand(commands, "感谢您的惠顾!\n");this.addTextToCommand(commands, "期待再次为您服务\n\n\n");// 切纸commands.push(0x1D, 0x56, 0x42, 0x00);// 转换为ArrayBufferconst buffer = new ArrayBuffer(commands.length);const dataView = new Uint8Array(buffer);commands.forEach((value, index) => {dataView[index] = value;});// 发送数据this.writeBLECharacteristicValue(buffer);},// 辅助方法:添加文本到命令数组addTextToCommand(commands, text) {for (let i = 0; i < text.length; i++) {commands.push(text.charCodeAt(i));}},// 写入蓝牙特征值writeBLECharacteristicValue(buffer) {wx.writeBLECharacteristicValue({deviceId: this.data.deviceId,serviceId: this.data.serviceId,characteristicId: this.data.characteristicId,value: buffer,success: (res) => {console.log('写入成功', res);this.setData({status: '打印指令已发送'});},fail: (err) => {console.error('写入失败', err);this.setData({status: '打印失败'});}});}
});

四、ESC/POS指令说明

  1. 打印机初始化:0x1B 0x40

  2. 设置对齐方式

    • 左对齐:0x1B 0x61 0x00
    • 居中:0x1B 0x61 0x01
    • 右对齐:0x1B 0x61 0x02
  3. 设置字体大小

    • 0x1D 0x21 0x00 - 正常大小
    • 0x1D 0x21 0x11 - 双倍宽高
  4. 换行0x0A

  5. 切纸0x1D 0x56 0x42 0x00

五、测试流程

  1. 打开微信开发者工具,导入本项目
  2. 点击"搜索蓝牙打印机"按钮
  3. 在设备列表中找到您的打印机并点击连接
  4. 连接成功后,点击"打印测试小票"测试基本功能
  5. 点击"打印订单"打印完整的订单信息

六、常见问题解决

  1. 找不到设备:

    • 确保打印机蓝牙已开启并可被发现
    • 检查打印机是否支持 BLE (蓝牙4.0及以上)
    • 修改代码中的设备名称过滤条件(如GBTP)
  2. 连接失败:

    • 确保打印机未被其他设备连接
    • 尝试重启打印机蓝牙
  3. 打印乱码:

    • 检查 ESC/POS 指令是否正确
    • 确保打印机支持接收的指令集
  4. 权限问题:

    • 确保小程序已获取蓝牙相关权限
    • 在手机上授权小程序使用蓝牙

七、进一步优化建议

  1. 添加打印机断开重连机制
  2. 实现打印任务队列,防止并发打印冲突
  3. 根据打印机型号调整指令集
  4. 添加打印状态回调,提供更好的用户反馈
  5. 实现打印内容模板化,便于维护

这个 Demo 提供了完整的蓝牙连接和打印功能实现,您可以根据实际需求进行调整和扩展。实际开发中,建议参考您使用的打印机型号的特定指令集文档进行微调。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/902849.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu opencv 安装

1.ubuntu opencv 安装 在Ubuntu系统中安装OpenCV&#xff0c;可以通过多种方式进行&#xff0c;以下是一种常用的安装方法&#xff0c;包括从源代码编译安装。请注意&#xff0c;安装步骤可能会因OpenCV的版本和Ubuntu系统的具体版本而略有不同。 一、安装准备 更新系统&…

【C++】class静态常量

Usage: static const T 1 background static const成员属于类&#xff0c;而不是类的实例&#xff0c;所以它们的初始化需要在类外进行(或者在C17之后可以用inline初始化)。 使用中可能遇到的情况&#xff1a; 在头文件中声明一个static const成员&#xff0c;然后在多个cpp…

Java 安全:如何防止 DDoS 攻击?

一、DDoS 攻击简介 DDoS&#xff08;分布式拒绝服务&#xff09;攻击是一种常见的网络攻击手段&#xff0c;攻击者通过控制大量的僵尸主机向目标服务器发送海量请求&#xff0c;致使服务器资源耗尽&#xff0c;无法正常响应合法用户请求。在 Java 应用开发中&#xff0c;了解 …

统计文件中单词出现的次数并累计

# 统计单词出现次数 fileopen("E:\Dasktape/python_test.txt","r",encoding"UTF-8") f1file.read() # 读取文件 countf1.count("is") # 统计文件中is 单词出现的次数 print(f"此文件中单词is出现了{count}次")# 2.判断单词出…

C语言实现贪心算法

一、贪心算法核心思想 特征&#xff1a;在每一步选择中都采取当前状态下最优&#xff08;局部最优&#xff09;的选择&#xff0c;从而希望导致全局最优解 适用场景&#xff1a;需要满足贪心选择性质和最优子结构性质 二、经典贪心算法示例 1. 活动选择问题 目标&#xff1a…

《一文读懂Transformers库:开启自然语言处理新世界的大门》

《一文读懂Transformers库:开启自然语言处理新世界的大门》 GitHub - huggingface/transformers: 🤗 Transformers: State-of-the-art Machine Learning for Pytorch, TensorFlow, and JAX. HF-Mirror Hello! Transformers快速入门 pip install transformers -i https:/…

Vue里面elementUi-aside 和el-main不垂直排列

先说解决方法 main.js少导包 import element-ui/lib/theme-chalk/index.css; //加入此行即可 问题复现 排查了一个小时终于找出来问题了&#xff0c;建议导包去看官方的文档&#xff0c;作者就是因为看了别人的导包流程导致的问题 导包官网地址Element UI导包快速入门

MYSQL 常用字符串函数 和 时间函数详解

一、字符串函数 1、​CONCAT(str1, str2, …) 拼接多个字符串。 SELECT CONCAT(Hello, , World); -- 输出 Hello World2、SUBSTRING(str, start, length)​​ 或 ​SUBSTR() 截取字符串。 SELECT SUBSTRING(MySQL, 3, 2); -- 输出 SQ3、LENGTH(str)​​ 与 ​CHAR_LENGTH…

Python-Agent调用多个Server-FastAPI版本

Python-Agent调用多个Server-FastAPI版本 Agent调用多个McpServer进行工具调用 1-核心知识点 fastAPI的快速使用agent调用多个server 2-思路整理 1&#xff09;先把每个子服务搭建起来2&#xff09;再暴露一个Agent 3-参考网址 VSCode配置Python开发环境&#xff1a;https:/…

Drools+自定义规则库

文章目录 前言一、创建规则库二、SpringBootDrools程序1.Maven依赖2.application.yml3.Mapper.xml4.Drools配置类5.Service6.Contoller7.测试接口 前言 公司的技术方案想搭建Drools自定义规则库配合大模型进行数据的校验。本篇用来记录使用SpringBoot配合Drools开发Demo程序。…

潮了 低配电脑6G显存生成60秒AI视频 本地部署/一键包/云算力部署/批量生成

最近发现了一个让人眼前一亮的工具——FramePack&#xff0c;它能用一块普通的6GB显存笔记本GPU&#xff0c;生成60秒电影级的高清视频画面&#xff0c;效果堪称炸裂&#xff01;那么我们就把他本地部署起来玩一玩、下载离线一键整合包&#xff0c;或者是用云算力快速上手。接下…

【蓝桥杯选拔赛真题104】Scratch回文数 第十五届蓝桥杯scratch图形化编程 少儿编程创意编程选拔赛真题解析

目录 scratch回文数 一、题目要求 1、准备工作 2、功能实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 四、程序编写 五、考点分析 六、推荐资料 1、scratch资料 2、python资料 3、C++资料 scratch回文数 第十五届青少年蓝桥杯scratch编…

大厂面试-框架篇

前言 本章内容来自B站黑马程序员java大厂面试题和小林coding 博主学习笔记&#xff0c;如果有不对的地方&#xff0c;海涵。 如果这篇文章对你有帮助&#xff0c;可以点点关注&#xff0c;点点赞&#xff0c;谢谢你&#xff01; 1.Spring 1.1 Spring框架中的单例bean是线程…

【AI 加持下的 Python 编程实战 2_10】DIY 拓展:从扫雷小游戏开发再探问题分解与 AI 代码调试能力(中)

文章目录 DIY 实战&#xff1a;从扫雷小游戏开发再探问题分解能力3 问题分解实战&#xff08;自顶向下&#xff09;3.2 页面渲染逻辑3.3 事件绑定逻辑 4 代码实现&#xff08;自底向上&#xff09;4.1 页面渲染部分4.2 事件绑定部分 写在前面 本篇将利用《Learn AI-assisted Py…

微信小程序开发1------微信小程序中的消息提示框总结

微信小程序中的消息提示框主要分为以下几种&#xff1a; 1. wx.showToast(Object object) 功能&#xff1a; 显示消息提示框&#xff0c;一般用于显示操作结果、状态等。 特点&#xff1a; 提示框显示在屏幕中间&#xff0c;持续一段时间后自动消失&#xff08;默认1.5秒&…

AI 场景落地:API 接口服务 VS 本地部署,哪种更适合?

在当前 AI 技术迅猛发展的背景下&#xff0c;企业在实现 AI 场景落地时&#xff0c;面临着一个关键抉择&#xff1a;是选择各大厂商提供的 API 接口服务&#xff0c;还是进行本地化部署&#xff1f;这不仅关乎成本、性能和安全性&#xff0c;还涉及到技术架构、数据治理和长期战…

Android 加壳应用运行流程 与 生命周期类处理方案

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ DexClassLoader DexClassLoader 可以加载任意路径下的 dex&#xff0c;或者 jar、apk、zip 文件&#xff08;包含classes.dex&#xff09;。常用于插件化、热…

c++进阶——类与继承

文章目录 继承继承的基本概念继承的基本定义继承方式继承的一些注意事项 继承类模板 基类和派生类之间的转换继承中的作用域派生类的默认成员函数默认构造函数拷贝构造赋值重载析构函数默认成员函数总结 不能被继承的类继承和友元继承与静态成员多继承及其菱形继承问题继承模型…

GAEA情感坐标背后的技术原理

基于GAEA的去中心化物理基础设施网络&#xff08;DePIN&#xff09;&#xff0c;用户有机会在GAEA平台上获得宝贵的数据共享积分。为了提升这些洞察的丰富性&#xff0c;用户必须花费一定数量的积分&#xff0c;将过去的网络数据与当前的情感数据绑定&#xff0c;从而产生一种新…

图形编辑器基于Paper.js教程27:对图像描摹的功能实现,以及参数调整

本篇文章来讲一下 图像描摹的功能的实现。 我们知道要雕刻图片可以通过分析图片的像素来生成相应的gcode进行雕刻&#xff0c;但如果你想要将图片转换为线稿进行雕刻&#xff0c;这个时候就要从图片中提取出 线稿。 例如下面的图片&#xff1a; 你想要获取到这个图片的线稿&…