基于Qt5的蓝牙打印开发实战:从扫描到小票打印的全流程

文章目录

  • 前言
  • 一、应用案例演示
  • 二、开发环境搭建
    • 2.1 硬件准备
    • 2.2 软件配置
  • 三、蓝牙通信原理剖析
    • 3.1 实现原理
    • 3.2 通信流程
    • 3.3 流程详解
    • 3.4 关键技术点
  • 四、Qt蓝牙核心类深度解析
    • 4.1 QBluetoothDeviceDiscoveryAgent
    • 4.2 QBluetoothDeviceInfo
    • 4.3 QBluetoothSocket
  • 五、功能实现关键步骤
    • 5.1 设备扫描与发现
    • 5.2 设备连接与状态管理
    • 5.3 打印数据封装与发送


前言

本文基于Qt5的蓝牙模块,详细讲解了Linux 下如何实现蓝牙设备扫描、连接、数据通信与打印功能的开发。文章内容涵盖核心类的解析、关键接口设计及讲解,助你快速掌握嵌入式蓝牙应用开发。


一、应用案例演示

演示视频之基于Qt5的蓝牙打印开发实战:从扫描到小票打印

二、开发环境搭建

2.1 硬件准备

  • Orange Pi开发板(RK3566芯片)
  • 支持SPP协议的蓝牙打印机

在这里插入图片描述
我使用的是香橙派的CM4开发板,您可以根据实际需求选择合适的开发板即可,系统信息如下所示:

root@orangepicm4:~# uname -a
Linux orangepicm4 5.10.160-rockchip-rk356x #1.0.6 SMP Mon May 27 17:03:18 CST 2024 aarch64 GNU/Linux
root@orangepicm4:~# cat /etc/issue
Orange Pi 1.0.6 Bullseye \l

而打印机方面,我选择的是这款便携式的热敏打印机:

在这里插入图片描述

2.2 软件配置

安装依赖:

sudo apt-get install libbluetooth-dev qtconnectivity5-dev

CMakeList.txt 配置:

find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Bluetooth REQUIRED)
target_link_libraries(BluetoothPrinterDemo PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Bluetooth)

三、蓝牙通信原理剖析

3.1 实现原理

蓝牙打印功能基于经典蓝牙(BR/EDR)的SPP协议(Serial Port Profile),核心流程如下:

1. 设备发现: 扫描周围蓝牙设备,筛选支持SPP协议的设备。
2. 建立连接: 通过设备的MAC地址和服务UUID(00001101-0000-1000-8000-00805F9B34FB)创建Socket连接。
3. 数据通信: 向打印机发送符合ESC/POS标准的指令集(文本、格式控制、切纸等)。
4. 资源释放: 断开连接并释放蓝牙资源。

3.2 通信流程

┌─────────────┐     ┌───────────────┐     ┌──────────────┐
│  启动扫描     │────>│ 发现蓝牙设备     │────>│ 显示设备列表    │
└─────────────┘     └───────────────┘     └──────────────┘│▼
┌─────────────┐     ┌───────────────┐     ┌──────────────┐
│ 用户选择设备  │────>│ 建立Socket连接   │───┬>│ 连接成功       │
└─────────────┘     └───────────────┘   │  └──────────────┘│             │▼             │
┌─────────────┐     ┌───────────────┐  │  ┌──────────────┐
│ 发送打印数据  │<────│  生成打印指令    │  └─┤ 连接失败/超时   │
└─────────────┘     └───────────────┘     └──────────────┘│▼
┌─────────────┐     ┌───────────────┐
│  断开连接     │<────│  完成打印任务    │
└─────────────┘     └───────────────┘

3.3 流程详解

设备发现阶段:

  • 调用QBluetoothDeviceDiscoveryAgent.start()启动扫描。
  • 过滤设备类型(仅保留经典蓝牙设备)。
  • 将设备信息(名称、MAC地址)显示在列表中。

连接阶段:

  • 用户选择设备后,通过QBluetoothSocket连接设备的SPP服务。
  • 设置超时监控(10秒未连接成功则自动取消)。

打印阶段:

  • 数据封装:组合文本内容与ESC/POS指令(如\x1B\x40初始化打印机)。
  • 编码处理:中文需转换为GBK编码(兼容大多数国产打印机)。
  • 数据发送:通过QBluetoothSocket.write()发送字节流。

断开连接:

  • 主动调用disconnectFromService()断开Socket。
  • 在析构函数中自动释放资源,防止内存泄漏。

3.4 关键技术点

步骤技术实现对应代码类/方法
设备扫描QBluetoothDeviceDiscoveryAgentstart()/deviceDiscovered()
连接管理QBluetoothSocket + 服务UUIDconnectToService()
数据封装ESC/POS指令集 + 编码转换QByteArray/QTextCodec
错误处理监听errorOccurred信号handleSocketError()

四、Qt蓝牙核心类深度解析

类名功能说明
QBluetoothDeviceDiscoveryAgent蓝牙设备扫描器,支持经典/低功耗双模式
QBluetoothDeviceInfo存储设备MAC地址、名称、信号强度等信息
QBluetoothSocket实现数据读写的核心通信通道

4.1 QBluetoothDeviceDiscoveryAgent

作用:
蓝牙设备扫描的核心控制器,负责发现周边可见的经典蓝牙设备(非BLE)。

关键方法:

方法作用代码示例
start()启动设备扫描m_discoveryAgent->start()
stop()停止扫描m_discoveryAgent->stop()
isActive()检查是否正在扫描if(m_discoveryAgent->isActive())

信号:

// 设备发现时触发
void deviceDiscovered(const QBluetoothDeviceInfo &info);// 扫描完成时触发
void finished();

在代码中的应用

// 初始化扫描器
m_discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);// 绑定设备发现信号
connect(m_discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,this, &BluetoothWindow::deviceDiscovered);// 启动扫描(代码截取自startScan())
m_discoveryAgent->start();
m_statusLabel->setText("正在扫描设备...");

关键实现细节:

  • 设备过滤: 通过coreConfigurations()筛选经典蓝牙设备
if(device.coreConfigurations() & QBluetoothDeviceInfo::BaseRateCoreConfiguration) {// 只显示传统蓝牙设备
}

4.2 QBluetoothDeviceInfo

作用:
存储蓝牙设备的完整信息,包括名称、MAC地址、支持的服务等。

关键属性获取方法:

方法返回值代码示例
name()设备名称(如"Printer-01")device.name()
address()MAC地址(QBluetoothAddress类型)device.address().toString()
serviceUuids()设备支持的服务UUID列表device.serviceUuids().contains(QBluetoothUuid::SerialPort)

在代码中的应用:

// 存储设备信息到列表项(deviceDiscovered()中)
QListWidgetItem *item = new QListWidgetItem(QString("%1 [%2]").arg(device.name()).arg(device.address().toString()));
item->setData(Qt::UserRole, QVariant::fromValue(device)); // 原始设备数据存储// 连接时获取设备信息(connectDevice()中)
m_currentDevice = item->data(Qt::UserRole).value<QBluetoothDeviceInfo>();

设计亮点:

  • 数据持久化:通过Qt::UserRole直接存储设备对象,避免后续从字符串重新解析MAC地址
  • 服务验证:连接前检查设备是否支持串口服务
if(!m_currentDevice.serviceUuids().contains(QBluetoothUuid::SerialPort)) {QMessageBox::warning(this, "错误", "设备不支持打印服务");
}

4.3 QBluetoothSocket

作用:
实现蓝牙协议栈的数据传输,支持RFCOMM(经典蓝牙)和L2CAP协议。

关键方法:

方法作用代码示例
connectToService()连接到指定服务socket->connectToService(addr, uuid)
disconnectFromService()断开连接socket->disconnectFromService()
write()发送数据socket->write(data)

重要信号:

void stateChanged(QBluetoothSocket::SocketState state); // 连接状态变化
void errorOccurred(QBluetoothSocket::SocketError error); // 错误发生时
void bytesWritten(qint64 bytes); // 数据成功写入时

在代码中的应用:

// 创建Socket对象(connectDevice()中)
m_socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);// 连接状态处理
connect(m_socket, &QBluetoothSocket::stateChanged,this, &BluetoothWindow::socketStateChanged);// 错误处理
connect(m_socket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::error),this, &BluetoothWindow::handleSocketError);// 发起连接(使用SerialPort服务UUID)
m_socket->connectToService(m_currentDevice.address(), QBluetoothUuid(QBluetoothUuid::SerialPort));

状态机详解:

状态值含义代码处理逻辑
QBluetoothSocket::UnconnectedState未连接显示"未连接"状态
QBluetoothSocket::ConnectingState正在连接显示"连接中…"
QBluetoothSocket::ConnectedState已连接启用打印按钮
QBluetoothSocket::ClosingState正在断开显示"断开中…"

五、功能实现关键步骤

5.1 设备扫描与发现

// BluetoothWindow.cpp
void BluetoothWindow::startScan() {m_deviceList->clear();m_discoveryAgent->start(); // 启动扫描m_statusLabel->setText("正在扫描设备...");// 扫描完成处理connect(m_discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, [this]() {m_statusLabel->setText(QString("找到%1个设备").arg(m_deviceList->count()));});
}void BluetoothWindow::deviceDiscovered(const QBluetoothDeviceInfo &device) {if (device.coreConfigurations() & QBluetoothDeviceInfo::BaseRateCoreConfiguration) {QListWidgetItem *item = new QListWidgetItem(QString("%1 [%2]").arg(device.name()).arg(device.address().toString()));item->setData(Qt::UserRole, QVariant::fromValue(device)); // 存储原始设备数据m_deviceList->addItem(item);}
}

关键点:

  • 通过QBluetoothDeviceDiscoveryAgent实现非阻塞设备扫描
  • 使用Qt::UserRole存储设备原始数据,避免后续连接时重复解析字符串
  • 过滤仅显示经典蓝牙设备(BaseRateCoreConfiguration)

5.2 设备连接与状态管理

void BluetoothWindow::connectDevice() {QListWidgetItem *item = m_deviceList->currentItem();if (!item) return;// 从Item中直接获取设备信息m_currentDevice = item->data(Qt::UserRole).value<QBluetoothDeviceInfo>();if (m_socket) m_socket->deleteLater();m_socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);// 连接状态信号绑定connect(m_socket, &QBluetoothSocket::stateChanged, this, &BluetoothWindow::socketStateChanged);// 连接超时处理(10秒)m_connectionTimer->start(10000);m_socket->connectToService(m_currentDevice.address(), QBluetoothUuid(QBluetoothUuid::SerialPort));
}void BluetoothWindow::socketStateChanged(QBluetoothSocket::SocketState state) {switch (state) {case QBluetoothSocket::ConnectedState:m_statusLabel->setText("已连接:" + m_currentDevice.name());enableControls(true);break;case QBluetoothSocket::UnconnectedState:enableControls(false);break;}
}

关键点:

  • 通过QBluetoothUuid::SerialPort指定串口协议(SPP)
  • 使用QTimer实现连接超时保护
  • 状态机管理连接流程(UI状态同步)

5.3 打印数据封装与发送

QByteArray BluetoothWindow::generatePrintData(CustomerInfo info) const
{// 获取当前日期QString currentDate = QDate::currentDate().toString("yyyy/MM/dd");const QString printData = QString("ID: %1\n""姓名: %2    性别: %3\n\n""OD(右眼):  DS %4\n""          DC %5 \n""          AX %6° \n""          SE %7 \n\n""OD(左眼):  DS %8\n""          DC %9 \n""          AX %10° \n""          SE %11 \n""瞳孔大小: (%12mm OD,%13mm OS)\n""瞳距:       (%14mm)\n""结果:       %15\n""日期: %16 (C) %17\n\n").arg(info.IdentityID)//1.arg(info.Name)//2.arg(info.Gender)//3.arg(info.reportData.RightEyeBallMirror) // 4 右眼 DS.arg(info.reportData.RightOphthlmoscope) // 5 右眼 DC.arg(info.reportData.RightEyeAxialPosition) // 6 右眼 AX.arg(info.reportData.RightEyeBallMirror + (info.reportData.RightOphthlmoscope/2)) // 7 右眼 SE.arg(info.reportData.LeftEyeBallMirror) // 8 左眼 DS.arg(info.reportData.LeftOphthlmoscope) // 9 左眼 DC.arg(info.reportData.LeftEyeAxialPosition) // 10 左眼 AX.arg(info.reportData.LeftEyeBallMirror + (info.reportData.LeftOphthlmoscope/2)) // 11 左眼 SE.arg(info.reportData.RightEyePupilSize) // 12 右眼瞳孔大小.arg(info.reportData.LeftEyePupilSize) // 13 左眼瞳孔大小.arg(info.reportData.PupillaryDistance) // 14 瞳距.arg(info.Result) // 15 结果.arg(currentDate) // 16 使用当天的日期.arg(info.hospital); // 17 医院// 添加中文支持检查和更完整的打印指令QByteArray data;data.append("\x1B\x40"); // 初始化//    data.append("\x1C\x2E"); // 中文模式//    data.append("\x1B\x21\x10"); // 设置字体大小// 使用更安全的编码检测if(QTextCodec::codecForName("GBK")) {QTextCodec *gbkCodec = QTextCodec::codecForName("GBK");data.append(gbkCodec->fromUnicode(printData));} else {data.append(printData.toLocal8Bit()); // 回退到本地编码}data.append("\n\n\x1D\x56\x41\x02"); // 更标准的切纸指令return data;
}

关键点:

  • 兼容GBK编码与本地编码回退机制
  • 使用ESC/POS标准指令集(\x1B\x40初始化,\x1D\x56\x41\x02切纸)

在这里插入图片描述

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

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

相关文章

高可靠性厚铜板制造的关键设备与工艺投入

随着科技的不断发展&#xff0c;电子设备越来越普及&#xff0c;对电路板的需求也越来越大。厚铜板电路板作为一种高性能、高可靠性的电路板&#xff0c;受到了广泛的关注和应用。那么&#xff0c;作为一家厚铜板电路板供应商&#xff0c;如何投入线路板生产呢&#xff1f;本文…

【如何使用solidwork编辑结构导入到simscope】

这里写自定义目录标题 尝试将solidrwork的模型导入到matlab中&#xff0c;以下是官方给出的设计步骤&#xff0c;冲啊 To use Simscape Multibody Link, you must install MATLAB and the CAD applications on the same computer. To ensure the successful installation of Si…

Linux 在个人家目录下添加环境变量 如FLINK_PROPERTIES=“jobmanager.rpc.address: jobmanager“

问题&#xff1a; Docker Flink Application Mode 命令行形式部署前&#xff0c;需要在Linux执行以下&#xff1a; $ FLINK_PROPERTIES"jobmanager.rpc.address: jobmanager" $ docker network create flink-network 临时变量只在当前session会话窗口生效&#xf…

spring项目rabbitmq es项目启动命令

应该很多开发者遇到过需要启动中间件的情况&#xff0c;什么测试服务器挂了&#xff0c;服务连不上nacos了巴拉巴拉的&#xff0c;虽然是测试环境&#xff0c;但也会手忙脚乱&#xff0c;疯狂百度。 这里介绍一些实用方法 有各种不同的场景&#xff0c;一是重启&#xff0c;服…

语音合成之七语音克隆技术突破:从VALL-E到SparkTTS,如何解决音色保真与清晰度的矛盾?

从VALL-E到SparkTTS&#xff0c;如何解决音色保真与清晰度的矛盾&#xff1f; 引言语音克隆技术发展史YourTTS&#xff1a;深入剖析架构与技术VALL-E&#xff1a;揭秘神经编解码语言模型MaskGCTSparkTTS&#xff1a;利用 LLM 实现高效且可控的语音合成特征解耦生成式模型特征解…

run code执行ts配置

1、全局安装typescript npm install –g typescript 执行tsc –v&#xff0c;可输出版本号&#xff0c;代表安装成功 2、创建tsConfig文件 npx tsc –init 创建成功目录下会出现tsconfig.json文件 3、安装ts-node&#xff0c;支持执行运行ts文件 npm install –g ts-node 控制…

splitchunk(如何将指定文件从主包拆分为单独的js文件)

1. 说明 webpack打包会默认将入口文件引入依赖js打包为一个入口文件&#xff0c;导致这个文件会比较大&#xff0c;页面首次加载时造成加载时间较长 可通过splitchunk配置相应的规则&#xff0c;对匹配的规则打包为单独的js,减小入口js的体积 2. 示例 通过正则匹配&#xff…

postgres 导出导入(基于数据库,模式,表)

在 PostgreSQL 中&#xff0c;导出和导入数据库、模式&#xff08;schema&#xff09;或表的数据可以使用多种工具和方法。以下是常用的命令和步骤&#xff0c;分别介绍如何导出和导入整个数据库、特定的模式以及单个表的数据。 一、导出数据 1. 使用 pg_dump 导出整个数据库…

第十一天 主菜单/设置界面 过场动画(Timeline) 成就系统(Steam/本地) 多语言支持

前言 对于刚接触Unity的新手开发者来说&#xff0c;构建完整的游戏系统往往充满挑战。本文将手把手教你实现游戏开发中最常见的四大核心系统&#xff1a;主菜单界面、过场动画、成就系统和多语言支持。每个模块都将结合完整代码示例&#xff0c;使用Unity 2022 LTS版本进行演示…

深入探索Python Pandas:解锁数据分析的无限可能

放在前头 深入探索Python Pandas&#xff1a;解锁数据分析的无限可能 深入探索Python Pandas&#xff1a;解锁数据分析的无限可能 在当今数据驱动的时代&#xff0c;高效且准确地处理和分析数据成为了各个领域的关键需求。而Python作为一门强大且灵活的编程语言&#xff0c;…

小集合 VS 大集合:MySQL 去重计数性能优化

小集合 VS 大集合&#xff1a;MySQL 去重计数性能优化 前言一、场景与问题 &#x1f50e;二、通俗执行流程对比三、MySQL 执行计划解析 &#x1f4ca;四、性能瓶颈深度剖析 &#x1f50d;五、终极优化方案 &#x1f3c6;六、总结 前言 &#x1f4c8; 测试结果&#xff1a; 在…

3、Linux操作系统下,linux的技术手册使用(man)

linux系统内置技术手册&#xff0c;方便开发人员查阅Linux相关指令&#xff0c;提升开发效率 man即是manual的前三个字母&#xff0c;有时候遇事不决&#xff0c;问个人&#xff08;man&#xff09; 其在线网址为&#xff1a;man 还有man网站的作者写的书&#xff0c;可以下…

京东商品详情数据爬取难度分析与解决方案

在当今数字化商业时代&#xff0c;电商数据对于市场分析、竞品研究、价格监控等诸多领域有着不可估量的价值。京东&#xff0c;作为国内首屈一指的电商巨头&#xff0c;其商品详情页蕴含着海量且极具价值的数据&#xff0c;涵盖商品价格、库存、规格、用户评价等关键信息。然而…

正确应对监管部门的数据安全审查

首席数据官高鹏律师团队编著 在当今数字化时代&#xff0c;数据安全已成为企业及各类组织面临的重要议题&#xff0c;而监管部门的数据安全审查更是关乎其生存与发展的关键挑战。随着法律法规的不断完善与监管力度的加强&#xff0c;如何妥善应对这一审查&#xff0c;避免潜在…

三星One UI安全漏洞:剪贴板数据明文存储且永不过期

三星One UI系统曝出重大安全漏洞&#xff0c;通过剪贴板功能导致数百万用户的敏感信息面临泄露风险。 剪贴板数据永久存储 安全研究人员发现&#xff0c;运行Android 9及以上系统的三星设备会将所有剪贴板内容——包括密码、银行账户详情和个人消息——以明文形式永久存储&am…

动态规划求解leetcode300.最长递增子序列(LIS)详解

给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 示例 1&#…

Rule.resourceQuery(通过路径参数指定loader匹配规则)

1. 说明 在 webpack 4 中&#xff0c;Rule.resourceQuery 是一个用于根据文件路径中的 查询参数&#xff08;query string&#xff09; 来匹配资源的配置项。它允许你针对带有特定查询条件的文件&#xff08;如 file.css?inline 或 image.png?raw&#xff09;应用不同的加载…

快速上手 MetaGPT

1. MetaGPT 简介 在当下的大模型应用开发领域&#xff0c;Agent 无疑是最炙手可热的方向&#xff0c;这也直接催生出了众多的 Agent 开发框架。在这之中&#xff0c; MetaGPT 是成熟度最高、使用最广泛的开发框架之一。 MetaGPT 是一款备受瞩目的多智能体开发框架&#xff0c…

新闻数据接口开发指南:从多源聚合到NLP摘要生成

随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;新闻行业也迎来了新的变革。AI不仅能够自动化生成新闻内容&#xff0c;还能通过智能推荐系统为用户提供个性化的新闻体验。万维易源提供的“新闻查询”API接口&#xff0c;结合了最新的AI技术&#xff0c;为开…

每天五分钟深度学习框架pytorch:使用visdom绘制损失函数图像

visdom的安装 pip install visdom如果安装失败 pip install --upgrade visdom开启visdom python -m visdom.server nohup python -m visdom.server后台启动然后就会出现,下面的页面,我们可以使用下面的链接打开visdom页面 Visdom中有两个重要概念: env环境。不同环境的可…