首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Android 藍牙通信接收十六進制數據並繪製波形圖完整實現指南

     編輯:程式設計疑難解答

需求:Android App 通過藍牙 (經典藍牙/低功耗藍牙) 接收十六進制數據流,實時繪製波形圖。本文提供從藍牙連接、數據解析、緩衝隊列、繪圖方案的完整工程化落地指南。

整體架構


[藍牙設備] → (RFID/GATT) → [Android BluetoothSocket/BluetoothGatt] 
    → [讀取線程: InputStream.read()/onCharacteristicChanged] 
    → [解析: Hex String → byte[] → float/int 數值] 
    → [環形緩衝區/ConcurrentQueue] 
    → [UI 線程: Handler/RunOnUiThread/Flow] 
    → [繪圖: Canvas + Path / MPAndroidChart / GraphView]

第一步:確認藍牙版本與協議

類型UUID/屬性Android API適用場景
經典藍牙 (SPP)固定 UUID: 00001101-0000-1000-8000-00805F9B34FBBluetoothSocket (RFCOMM)串口透傳、HC-05/06、傳統醫療/工業設備
低功耗藍牙 (BLE/GATT)自定義 Service/Characteristic UUID (128-bit)BluetoothGatt, BluetoothLeScanner現代感測器、穿戴設備、Nordic/ESP32 BLE

題目數據樣本 顯示 `S500\r\n` `B128\r\n` `Q466\r\n` 等 ASCII 文本行,極大概率為 **經典藍牙 SPP 透傳**,每行一個採樣點,`S`=採樣、`B`/`Q`=標記/事件。

第二步:經典藍牙 SPP 連接與讀取 (核心代碼)


// 1. 獲取 BluetoothAdapter
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();

// 2. 獲取遠程設備 (已配對或掃描)
BluetoothDevice device = btAdapter.getRemoteDevice("MAC_ADDRESS");

// 3. 建立 RFCOMM Socket (需在子線程)
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(
    UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
socket.connect(); // 阻塞直到連接成功

// 4. 獲取輸入流,啟動讀取線程
InputStream inputStream = socket.getInputStream();
new Thread(() -> {
    byte[] buffer = new byte[1024];
    int bytes;
    StringBuilder lineBuilder = new StringBuilder();
    while (!Thread.currentThread().isInterrupted()) {
        try {
            bytes = inputStream.read(buffer);
            if (bytes > 0) {
                String chunk = new String(buffer, 0, bytes, StandardCharsets.US_ASCII);
                lineBuilder.append(chunk);
                
                // 按行分割處理 (\r\n 或 \n)
                int newlineIndex;
                while ((newlineIndex = lineBuilder.indexOf("\n")) != -1) {
                    String line = lineBuilder.substring(0, newlineIndex).trim(); // 去除 \r
                    lineBuilder.delete(0, newlineIndex + 1);
                    
                    parseAndEnqueue(line); // 解析並入隊
                }
            }
        } catch (IOException e) {
            // 連接斷開處理
            break;
        }
    }
}).start();

第三步:數據解析 (針對題目樣本格式)


// 共享緩衝隊列 (生產者: 藍牙線程, 消費者: UI/繪圖線程)
private final BlockingQueue dataQueue = new ArrayBlockingQueue<>(2000);

private void parseAndEnqueue(String line) {
    if (line.isEmpty()) return;
    char prefix = line.charAt(0);
    String valStr = line.substring(1); // 去掉前綴 S/B/Q
    
    try {
        float value = Float.parseFloat(valStr); // "500" → 500.0f
        // 根據業務邏輯決定是否入隊
        if (prefix == 'S') { // 僅採樣數據繪圖
            if (!dataQueue.offer(value)) {
                dataQueue.poll(); // 滿則丟最舊
                dataQueue.offer(value);
            }
        }
        // B/Q 可作為事件標記單獨處理
    } catch (NumberFormatException ignored) {}
}

第四步:波形繪製方案對比

方案優點缺點適用場景
自定義 View + Canvas零依賴、完全可控、效能最高、支援幾萬點流式需自寫座標映射、網格、縮放、觸控高頻 (100Hz+)、長時長、嵌入式風格
MPAndroidChart功能豐富(縮放、游標、動畫)、社區成熟大數據量(>5000點) 卡頓、方法數大中低頻、需豐富交互、快速原型
GraphView輕量、API 簡單、支援實時滾動自定義程度低、大數據量效能一般簡單實時波形、教學/演示

自定義 View 核心繪製邏輯 (高性能流式)


public class WaveformView extends View {
    private final float[] buffer = new float[2000]; // 環形緩衝
    private int writePos = 0;
    private float minVal = -1000, maxVal = 1000; // 動態或固定量程
    
    public void addPoint(float v) {
        buffer[writePos] = v;
        writePos = (writePos + 1) % buffer.length;
        postInvalidateOnAnimation(); // 觸發重繪
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int w = getWidth(), h = getHeight();
        if (w == 0 || h == 0) return;
        
        Path path = new Path();
        float xStep = (float) w / buffer.length;
        float yZero = h * 0.5f; // 中線
        float scale = h / (maxVal - minVal) * 0.8f; // 留邊距
        
        // 從最舊點開始畫 (writePos 是最新點的下一個)
        for (int i = 0; i < buffer.length; i++) {
            int idx = (writePos + i) % buffer.length;
            float x = i * xStep;
            float y = yZero - (buffer[idx] - minVal) * scale;
            if (i == 0) path.moveTo(x, y);
            else path.lineTo(x, y);
        }
        canvas.drawPath(path, paint);
    }
}

第五步:線程安全與生命周期

  • 權限:`BLUETOOTH`, `BLUETOOTH_ADMIN`, `BLUETOOTH_CONNECT` (Android 12+), `BLUETOOTH_SCAN` (12+)。
  • 子線程連接/讀取:嚴禁在主線程 `socket.connect()` 或 `read()`。
  • 關閉釋放:`onDestroy` 中關閉 `socket`、`inputStream`、中斷讀取線程、`handler.removeCallbacks`。
  • UI 更新:`view.postInvalidateOnAnimation()` 或 `runOnUiThread`,避免非 UI 線程操作 View。

第六步:BLE (GATT) 版本要點 (若設備為 BLE)

  1. `BluetoothLeScanner.startScan` 掃描 → `connectGatt` 連接。
  2. `onServicesDiscovered` → 找到目標 `BluetoothGattCharacteristic` (需 `PROPERTY_NOTIFY`)。
  3. `gatt.setCharacteristicNotification(characteristic, true)` + 寫入 `CCCD` 描述符 `0x2902` = `0x0001` (ENABLE_NOTIFICATION_VALUE)。
  4. `onCharacteristicChanged` 回調中 `characteristic.getValue()` 獲取 `byte[]`,同樣解析入隊。

總結

  1. 確認設備為 **經典藍牙 SPP** (題目數據特徵明顯) → 用 `BluetoothSocket` + `InputStream` 讀取。
  2. 按行解析 ASCII 文本 (`S500\r\n` → `500.0f`),存入 **環形緩衝隊列**。
  3. 繪圖選 **自定義 View + Canvas** (高頻/長時長) 或 **MPAndroidChart** (快速/交互豐富)。
  4. 嚴格管理線程、權限、生命周期,做好異常重連機制。

參考開源專案:MPAndroidChart、GraphView、SimpleBluetoothTerminal (完整 SPP 終端示例)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved