Android Studio里的BLE數據接收策略

#本人是初次接觸Android藍牙開發,若有不對地方,歡迎指出。

#由于是講接收數據策略(其中還包含數據發送的部分策略),因此其他問題部分不會講述,只描述數據接收。

簡介(對于客戶端---手機端)

博主在處理數據接收的時候,發現BLE的數據接收有很多不一樣的地方,就是BLE的數據接收是采用它自己的MTU格式,目前一次BLE數據接收只能接收20字節數據,那么我們一旦涉及到超出20字節的數據,那么BLE的接收數據是按照20字節來劃分 --- 我們想要發送的數據,那么這個完整的數據被分成多個包的數據包。我們就是想處理完整的數據,而不是處理某一個單一的數據包,所以我們遇到的難點就是——怎么將接收到分開的數據包,整理成完整的數據來進行處理。

對于MTU的解釋

從字面上來說,MTU 是英文 Maximum Transmission Unit 的縮寫,即最大傳輸單元,它的單位是字節,指的是數據鏈路層的最大payload,由硬件網卡設置MTU,是一個硬性限制。

1.數據接收部分

引用別的博主的對于BLE的解釋——這里我就簡要介紹一下。

對于BLE最重要的部分就是 GATT回調(處理連接和服務發現,以及數據接收以及發送)。所以我們目前要用到 BluetoothGattCallback 以及 它里面的回調函數。

 private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {}@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}};

對于gattCallback 它的兩個回調函數有---

@Override

onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {}

@Override
? ?public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}

第一個是用于數據接收,也就是我們這個blog的重點部分,它也是一次只能接收一個20字節的數據分包。

第二個是用于處理數據是否寫出,當你的數據被發送出去的時候會進行回調。(注意了,它也是遵循MTU的,它也是一次性只能發送20個字節。)

2.對于onCharacteristicChanged的處理

首先可以看看我的寫的案例。

這個函數仍然是在

private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {

? ? //我們的?onCharacteristicChanged()在這里面進行重寫。

};

onCharacteristicChanged
        @Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {// value為設備發送的數據,根據數據協議進行解析byte[] value = characteristic.getValue();String received = new String(value);processReceivedData(value);Log.i(TAG,"接收到數據: " + received);
//            Log.d(TAG, "收到分段數據: " + new String(value, StandardCharsets.UTF_8));
//            runOnUiThread(() -> processReceivedData(value));}

?注釋的部分不用管,這個是我進行測試時候用的。

首先,我們可以看到value是為對應的BLE設備發送過來的數據,我們使用characteristic.getValue()接收,你可以看到這個函數里的變量characteristic 這個不用管,你直接使用這個函數就可以了。

然后,我們接收到的數據,還是byte,就是字節格式的數據包。

現在有兩種方式來進行數據包整合:

  1. 字節直接整合為完整數據。
  2. 將字節先轉換為字符串格式后再整合為字節完整數據。

根據自己的需求來進行選擇,byte格式是可以轉換為字節格式的。

1.對于字節直接整合為完整數據

// 首先在gattCallback這個變量外面 先申明一個全局變量
private List<byte[]> packets = new ArrayList<>();//作為數據緩存存儲@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {byte[] value = characteristic.getValue();packets.add(value);//  這里加入你自己的數據末尾判斷if(End){byte[] Alldata = GetWholeData(packets); //組成完整的數據/*下面同時可以加入你想在數據組合完整時,調用數據處理函數(可選部分)*/}}

對于數據整合函數

// 組裝數據
private byte[] GetWholeData(List<byte[]> packets) {ByteArrayOutputStream DataStream = new ByteArrayOutputStream();for (byte[] packet : packets) {DataStream.write(packet);}return DataStream.toByteArray();
}

2.將字節先轉換為字符串格式后再整合為字節完整數據

以我的案例為例子來進行講解。

        @Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {// value為設備發送的數據,根據數據協議進行解析byte[] value = characteristic.getValue();
//             String received = new String(value);
//            可加可不加,我用來調試的/* 主要核心函數 這個兼具數據處理*/processReceivedData(value);//            Log.i(TAG,"接收到數據: " + received);}

這個函數其實和第一個差不多,主要是數據處理部分,不相同。

對于ProcessReceivedData函數

    private final StringBuilder dataBuffer = new StringBuilder();
// 對于dataBuffer是用來進行完整數據拼包的,全局變量private void processReceivedData(byte[] data) {String packet = new String(data, StandardCharsets.UTF_8);dataBuffer.append(packet);// 前兩個部分,就是用來進行接收的數據包合包操作。 /* 后面是作為數據處理部分,你可以理解為前面的操作是合包只有真正完成數據包整合后,才會真正進行數據處理部分。下面的代碼作為案例,你可以自己改寫。
*/while (true) {int startIndex = dataBuffer.indexOf("#");if (startIndex == -1) {dataBuffer.setLength(0);break;}int endIndex = dataBuffer.indexOf("\n", startIndex);if (endIndex == -1) {String remaining = dataBuffer.substring(startIndex);dataBuffer.setLength(0);dataBuffer.append(remaining);break;}String rawData = dataBuffer.substring(startIndex, endIndex + 1);dataBuffer.delete(0, endIndex + 1);if (!rawData.startsWith("#") || !rawData.endsWith("\n")) {Log.e(TAG, "協議格式錯誤: " + rawData);continue;}try {String jsonStr = rawData.substring(1, rawData.length() - 1);JSONObject json = new JSONObject(jsonStr);double lat = json.getDouble("lat");double lon = json.getDouble("lon");float angle = (float) json.getDouble("angle");if (lastLat != null && lastLon != null) {CaneVectorControl.ControlCommand command = CaneVectorControl.computeControl(lastLat, lastLon, endLat, endLon, lat, lon);String jsonCommand = "#" + command.toJson() + "\n";sendCommand(jsonCommand);Log.i(TAG, "控制指令已發送: " + jsonCommand);} else {Log.e(TAG, "用戶位置未更新,無法計算指令");}} catch (JSONException e) {Log.e(TAG, "JSON解析失敗: " + e.getMessage() + "\n原始數據: " + rawData);}}}
if (!rawData.startsWith("#") || !rawData.endsWith("\n")) {Log.e(TAG, "協議格式錯誤: " + rawData);continue;
}

這一部分是用于檢測發送的數據格式是否正確。上面的代碼,可以按照注釋理解,不符合條件就結束循環,提前進行下一次的數據包處理。

try {String jsonStr = rawData.substring(1, rawData.length() - 1);JSONObject json = new JSONObject(jsonStr);double lat = json.getDouble("lat");double lon = json.getDouble("lon");float angle = (float) json.getDouble("angle");if (lastLat != null && lastLon != null) {CaneVectorControl.ControlCommand command = CaneVectorControl.computeControl(lastLat, lastLon, endLat, endLon, lat, lon);String jsonCommand = "#" + command.toJson() + "\n";sendCommand(jsonCommand);Log.i(TAG, "控制指令已發送: " + jsonCommand);} else {Log.e(TAG, "用戶位置未更新,無法計算指令");}
} catch (JSONException e) {Log.e(TAG, "JSON解析失敗: " + e.getMessage() + "\n原始數據: " + rawData);
}

對于 try{}catch{}部分就是我所提及的數據處理部分。? 你可以自己更改。

對于里面的一個 sendCommand(jsonCommand) 是處理完數據后發送相應的數據。

這里我再加上我們一般都會處理完數據后發送想要的相應部分。

3.對于數據接收處理后,數據發送部分

可以看到之前博主提及過的

@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {if (status == BluetoothGatt.GATT_SUCCESS) {Log.i(TAG, "寫入成功:" + new String(characteristic.getValue(), StandardCharsets.UTF_8));sendNextPacket();} else {Log.e(TAG, "寫入失敗,狀態碼: " + status);}
}

這個就是我們接下來要使用的數據發送回調。

先看我寫的完整例子。

SendCommand函數

    private Queue<byte[]> writeQueue = new LinkedList<>();@SuppressLint("MissingPermission")public void sendCommand(String command) {if (bluetoothGatt == null || writeCharateristic == null) {Log.e(TAG, "GATT未連接或寫特征不可用");return;}byte[] data = command.getBytes(StandardCharsets.UTF_8);int maxLength = 20; // 或 MTU - 3 // 博主使用的是HC-08 BLE 它的MTU大小為20字節// 清空隊列并分包writeQueue.clear();for (int i = 0; i < data.length; i += maxLength) {int end = Math.min(data.length, i + maxLength);byte[] chunk = Arrays.copyOfRange(data, i, end);writeQueue.offer(chunk);}// 發送第一個包sendNextPacket();}

sendNextPacket()函數

    @SuppressLint("MissingPermission")private void sendNextPacket() {if (writeQueue.isEmpty()) return;byte[] chunk = writeQueue.poll();writeCharateristic.setValue(chunk);boolean success = bluetoothGatt.writeCharacteristic(writeCharateristic);Log.i(TAG, "發送分包: " + new String(chunk, StandardCharsets.UTF_8) + ",成功: " + success);// 如果寫失敗,考慮重試機制}

onCharacteristicWrite()函數

        @Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {if (status == BluetoothGatt.GATT_SUCCESS) {Log.i(TAG, "寫入成功:" + new String(characteristic.getValue(), StandardCharsets.UTF_8));sendNextPacket();
// 如果是還沒有發送完數據,那么就繼續發送它的下一個數據包} else {Log.e(TAG, "寫入失敗,狀態碼: " + status);}}

問題的本質就是:

BLE 發送數據包時,每包最大只能發 20 字節(傳統 BLE 協議限制),你的完整字符串超過這個長度就會被自動分包。

解決方案:和接收端一樣,用緩存緩沖區來拼接這些碎片

我們在 發送端什么都不用改(只要發送時不要太快),只需要在 接收端緩沖所有片段,直到遇到 \n 表示完整數據包結尾。(這里可以根據你自己定義的協議更改結尾)。

以上就是我作為小白開發BLE接收數據的感悟,希望對你有所幫助。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/83012.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/83012.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/83012.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【第4章 圖像與視頻】4.2 圖像的縮放

文章目錄 前言示例-圖像的縮放在 Canvas 邊界之外繪制圖像 前言 在上節中讀者已經學會了如何使用 drawImage() 方法將一幅未經縮放的圖像繪制到 canvas 之中。現在我們就來看看如何用該方法在繪制圖像的時候進行縮放 示例-圖像的縮放 未縮放的圖像&#xff0c;顯示圖形原有大…

[網頁五子棋][用戶模塊]客戶端開發(登錄功能和注冊功能)

文章目錄 客戶端開發登錄功能htmlcsscommon.csslogin.css jQuery引入 jquery 運行程序注冊功能 客戶端開發 登錄功能 html <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport&…

【Doris基礎】Apache Doris業務場景全解析:從實時數倉到OLAP分析的完美選擇

目錄 1 Doris核心能力概述 2 實時數據分析場景 2.1 實時數據倉庫 2.2 實時監控與告警 3 交互式OLAP分析場景 3.1 自助式BI分析 3.2 用戶行為分析 4 大數據分析場景 4.1 日志分析系統 4.2 時序數據處理 5 Doris技術架構適配性分析 5.1 適合Doris的場景特征 5.2 不適合Doris的場景…

C# 類和繼承(類繼承和訪問繼承的成員)

類繼承 通過繼承可以定義一個新類&#xff0c;新類納入一個已經聲明的類并進行擴展。 可以使用一個已經存在的類作為新類的基礎。已存在的類稱為基類&#xff08;base class&#xff09;&#xff0c;新類稱 為派生類&#xff08;derived class&#xff09;。派生類成員的組成如…

ElasticSearch查詢指定時間內出現的次數/2秒內出現的次數

查詢指定時間內出現的次數 POST process-log/_search {"size": 0,"query": {"bool": {"filter": [{"range": {"requestTime": {"from": 1722470400000,"to": 1722556800000}}}]}},"agg…

第四十五節:目標檢測與跟蹤-Meanshift/Camshift 算法

引言 在計算機視覺領域,目標跟蹤是實時視頻分析、自動駕駛、人機交互等應用的核心技術之一。Meanshift和Camshift算法作為經典的跟蹤方法,以其高效性和實用性廣受關注。本文將從原理推導、OpenCV實現到實際案例,全面解析這兩種算法的核心思想與技術細節。 一、Meanshift算法…

Typora-macOS 風格代碼塊

效果&#xff1a; 替換 Typora安裝目錄中 themes 文件夾下的 base.user.css 文件&#xff0c;直接替換即可&#xff0c;建議先備份。 css&#xff1a; /* 語法高亮配色 */ .CodeMirror-line .cm-number { color: #b5cea8; } /* 數字 - 淺綠色 */ .CodeMirror-line .…

【高頻面試題】數組中的第K個最大元素(堆、快排進階)

文章目錄 數組中的第K個最大元素題目描述示例1示例2提示&#xff1a; 解法1&#xff08;堆維護前k大元素&#xff09;解法2 手寫堆維護解法3&#xff08;快速選擇算法&#xff09;例題&#xff1a;P1923 【深基9.例4】求第 k 小的數參考 數組中的第K個最大元素 題目描述 給定…

『uniapp』添加桌面長按快捷操作 shortcuts(詳細圖文注釋)

目錄 手機環境適配說明安卓效果圖代碼 iOS(暫未實測,沒有水果開發者)總結 歡迎關注 『uniapp』 專欄&#xff0c;持續更新中 歡迎關注 『uniapp』 專欄&#xff0c;持續更新中 手機環境適配說明 個別手機系統可能需要進行特別的權限設置,否則會無法使用 桌面快捷方式: 已知的有…

PHP 垃圾回收高級特性

PHP 垃圾回收高級特性 1. 循環引用與內存泄漏 單純的引用計數在遇到循環引用時會導致內存泄漏&#xff0c;主要原因是引用計數無法正確識別那些僅通過循環引用相互關聯但實際上已經不可達的對象。 1.1 引用計數的基本原理 引用計數是一種內存管理機制&#xff0c;通過維護每…

奈雪小程序任務腳本

功能概述 該腳本用于自動完成奈雪點單小程序的每日任務&#xff0c;包括&#xff1a; 自動檢測 Token 有效性自動簽到&#xff08;如果未簽到&#xff09;獲取用戶基礎信息&#xff08;昵稱、手機號&#xff09;查詢當前奈雪幣余額記錄連續簽到天數支持多賬號執行&#xff0c…

基于cornerstone3D的dicom影像瀏覽器 第二十七章 設置vr相機,復位視圖

文章目錄 前言一、VR視圖設置相機位置1. 相機位置參數2. 修改mprvr.js3. 調用流程1) 修改Toolbar3D.vue2) 修改View3d.vue3) 修改DisplayerArea3D.vue 二、所有視圖復位1.復位流程說明2. 調用流程1) Toolbar3D中添加"復位"按鈕&#xff0c;發送reset事件2) View3d.vu…

Opencv4 c++ 自用筆記 03 滑動條、相機與視頻操作

1. 相機與視頻操作 1.1 打開視頻&#xff0f;相機 OpenCV 中 imread() 只能讀取靜態圖像&#xff0c;若要讀取視頻文件或攝像頭流&#xff0c;需要使用 VideoCapture 類&#xff1a; // 構造函數 cv::VideoCapture::VideoCapture(); cv::VideoCapture…

身份證發給別人怎么加水印?賽文奧特曼身份證添加水印教程

我們經常需要使用身份證照片進行身份驗證、資料提交等操作。然而&#xff0c;直接將身份證照片發送給他人或上傳到網絡存在一定的信息泄露風險。為了更好地保護個人隱私&#xff0c;我們可以使用 簡鹿水印助手 這款工具&#xff0c;在身份證照片上添加專屬水印&#xff0c;從而…

十、【核心功能篇】項目與模塊管理:前端頁面開發與后端 API 聯調實戰

【核心功能篇】項目與模塊管理&#xff1a;前端頁面開發與后端 API 聯調實戰 前言準備工作第一部分&#xff1a;完善項目管理功能 (Project)1. 創建/編輯項目的表單對話框組件 第二部分&#xff1a;模塊管理功能 (集成到項目詳情頁)1. 創建模塊相關的 API 服務 (src/api/module…

ES分詞搜索

ES的使用 前言作者使用的版本作者需求 簡介ES簡略介紹ik分詞器簡介 使用es的直接簡單使用es的查詢 es在java中使用備注說明 前言 作者使用的版本 es: 7.17.27spring-boot-starter-data-elasticsearch: 7.14.2 作者需求 作者接到一個業務需求&#xff0c;我們系統有份數據被…

Axure設計案例——科技感立體柱狀圖

想讓你的數據展示告別平淡無奇&#xff0c;成為吸引全場目光的焦點嗎&#xff1f;快來瞧瞧這個Axure設計的科技感立體柱狀圖案例&#xff01;科技感設計風格借助逼真的立體效果打破傳統柱狀圖的平面感&#xff0c;營造出一種令人眼前一亮的視覺震撼。每一個柱狀體都仿佛是真實存…

惡意npm與VS Code包竊取數據及加密貨幣資產

60個npm包竊取系統敏感信息 安全研究人員在npm軟件包注冊表中發現60個惡意組件&#xff0c;這些組件能夠收集主機名、IP地址、DNS服務器和用戶目錄信息&#xff0c;并將其發送至Discord平臺控制的終端節點。據Socket安全研究員Kirill Boychenko上周發布的報告顯示&#xff0c;…

leetcode 2359. 找到離給定兩個節點最近的節點

給你一個 n 個節點的 有向圖 &#xff0c;節點編號為 0 到 n - 1 &#xff0c;每個節點 至多 有一條出邊。 有向圖用大小為 n 下標從 0 開始的數組 edges 表示&#xff0c;表示節點 i 有一條有向邊指向 edges[i] 。如果節點 i 沒有出邊&#xff0c;那么 edges[i] -1 。 同時…

1. pytorch手寫數字預測

1. pytorch手寫數字預測 1.背景2.準備數據集2.定義模型3.dataloader和訓練4.訓練模型5.測試模型6.保存模型 1.背景 因為自身的研究方向是多模態目標跟蹤&#xff0c;突然對其他的視覺方向產生了興趣&#xff0c;所以心血來潮的回到最經典的視覺任務手寫數字預測上來&#xff0…