基于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.pswp.cn/diannao/80781.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/80781.shtml
英文地址,請注明出處:http://en.pswp.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環境。不同環境的可…