HarmonyOS APP开发RFCOMM通信

举报
Jack20 发表于 2026/06/19 20:15:12 2026/06/19
【摘要】 HarmonyOS APP开发RFCOMM通信BLE适合小数据量的传感器场景,但如果你需要传输音频、文件或者建立类似串口的通信,那就得请出"老大哥"——经典蓝牙了。今天咱们就来聊聊鸿蒙系统中的经典蓝牙开发,特别是RFCOMM协议的使用,看看如何实现设备间的流式数据传输。 一、背景与动机 1.1 经典蓝牙与BLE的区别先来搞清楚两者的定位差异:对比项经典蓝牙BLE蓝牙传输速率1-3 Mbps...

HarmonyOS APP开发RFCOMM通信

BLE适合小数据量的传感器场景,但如果你需要传输音频、文件或者建立类似串口的通信,那就得请出"老大哥"——经典蓝牙了。今天咱们就来聊聊鸿蒙系统中的经典蓝牙开发,特别是RFCOMM协议的使用,看看如何实现设备间的流式数据传输。

一、背景与动机

1.1 经典蓝牙与BLE的区别

先来搞清楚两者的定位差异:

对比项 经典蓝牙 BLE蓝牙
传输速率 1-3 Mbps ~1 Mbps
连接时间 数秒 毫秒级
功耗 高(通话级) 极低
典型应用 音频、文件传输、串口通信 传感器、控制指令
协议栈 RFCOMM、SDP、L2CAP等 GATT、ATT、GAP
拓扑 点对点 星型拓扑

经典蓝牙的典型应用

  1. 音频传输:蓝牙耳机、音箱、车载系统
  2. 文件传输:手机间文件分享(虽然现在用得少了)
  3. 串口模拟:打印机、POS机、工业设备通信
  4. 网络共享:蓝牙 tethering

1.2 RFCOMM协议简介

RFCOMM是经典蓝牙的核心协议之一,它提供了基于L2CAP的串口模拟:
图片.png

RFCOMM核心概念

  • Channel(通道):类似TCP端口,用于区分不同的服务
  • Server Socket:服务端监听,等待连接
  • Client Socket:客户端连接,发起通信
  • SDP(Service Discovery Protocol):服务发现,让客户端找到服务端

二、核心原理

2.1 RFCOMM通信流程

图片.png

2.2 SDP服务记录

SDP记录描述了服务的属性,让客户端能够发现和识别服务:

interface SdpRecord {
  // 服务UUID
  serviceUuid: string;
  
  // 服务名称
  serviceName: string;
  
  // 服务描述
  serviceDescription?: string;
  
  // 服务提供者
  providerName?: string;
  
  // RFCOMM通道号
  rfcommChannel: number;
  
  // 支持的协议
  protocols: string[];
}

2.3 Socket通信模型

RFCOMM的Socket通信与TCP Socket类似:

// Socket状态
enum SocketState {
  CLOSED = 0,      // 已关闭
  LISTENING = 1,   // 监听中
  CONNECTING = 2,  // 连接中
  CONNECTED = 3,   // 已连接
  ERROR = 4        // 错误
}

// Socket事件
interface SocketEvent {
  type: 'connect' | 'data' | 'error' | 'close';
  data?: ArrayBuffer;
  error?: Error;
}

三、代码实战

3.1 经典蓝牙管理类封装

import bluetooth from '@ohos.bluetooth';
import socket from '@ohos.net.socket';
import { BusinessError } from '@ohos.base';

/**
 * 经典蓝牙设备信息
 */
export interface ClassicBluetoothDevice {
  // 设备地址
  deviceId: string;
  
  // 设备名称
  deviceName: string;
  
  // 设备类型
  deviceClass: number;
  
  // 配对状态
  bondState: bluetooth.BondState;
  
  // 信号强度
  rssi?: number;
}

/**
 * 经典蓝牙管理器
 * 封装设备发现、配对、RFCOMM通信等功能
 */
export class ClassicBluetoothManager {
  private static instance: ClassicBluetoothManager;
  
  // 已发现的设备
  private discoveredDevices: Map<string, ClassicBluetoothDevice> = new Map();
  
  // RFCOMM服务端Socket
  private serverSocket: bluetooth.BluetoothSocket | null = null;
  
  // RFCOMM客户端Socket
  private clientSocket: bluetooth.BluetoothSocket | null = null;
  
  // 回调函数
  private onDeviceFound: ((device: ClassicBluetoothDevice) => void) | null = null;
  private onBondStateChanged: ((device: ClassicBluetoothDevice) => void) | null = null;
  private onConnectionChanged: ((state: number) => void) | null = null;
  private onDataReceived: ((data: ArrayBuffer) => void) | null = null;

  private constructor() {
    this.initEventListeners();
  }

  /**
   * 获取单例实例
   */
  public static getInstance(): ClassicBluetoothManager {
    if (!ClassicBluetoothManager.instance) {
      ClassicBluetoothManager.instance = new ClassicBluetoothManager();
    }
    return ClassicBluetoothManager.instance;
  }

  /**
   * 初始化事件监听
   */
  private initEventListeners(): void {
    // 监听设备发现
    bluetooth.on('bluetoothDeviceFind', (devices: Array<bluetooth.DeviceInfo>) => {
      devices.forEach((device) => {
        const deviceInfo: ClassicBluetoothDevice = {
          deviceId: device.deviceId,
          deviceName: device.deviceName || 'Unknown',
          deviceClass: device.deviceClass,
          bondState: bluetooth.BondState.BOND_STATE_INVALID
        };
        
        this.discoveredDevices.set(deviceInfo.deviceId, deviceInfo);
        
        if (this.onDeviceFound) {
          this.onDeviceFound(deviceInfo);
        }
      });
    });

    // 监听配对状态变化
    bluetooth.on('bondStateChange', (data: { deviceId: string, bondState: bluetooth.BondState }) => {
      const device = this.discoveredDevices.get(data.deviceId);
      if (device) {
        device.bondState = data.bondState;
        if (this.onBondStateChanged) {
          this.onBondStateChanged(device);
        }
      }
    });

    // 监听蓝牙状态变化
    bluetooth.on('stateChange', (state: bluetooth.BluetoothState) => {
      console.info(`[经典蓝牙] 状态变化: ${state}`);
    });
  }

  /**
   * 检查蓝牙是否开启
   */
  public async isBluetoothEnabled(): Promise<boolean> {
    try {
      const state = bluetooth.getState();
      return state === bluetooth.BluetoothState.STATE_ON;
    } catch (error) {
      console.error('[经典蓝牙] 检查状态失败:', error);
      return false;
    }
  }

  /**
   * 开启蓝牙
   */
  public async enableBluetooth(): Promise<boolean> {
    try {
      await bluetooth.enableBluetooth();
      console.info('[经典蓝牙] 已开启');
      return true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[经典蓝牙] 开启失败: ${err.message}`);
      return false;
    }
  }

  /**
   * 关闭蓝牙
   */
  public async disableBluetooth(): Promise<boolean> {
    try {
      await bluetooth.disableBluetooth();
      console.info('[经典蓝牙] 已关闭');
      return true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[经典蓝牙] 关闭失败: ${err.message}`);
      return false;
    }
  }

  /**
   * 开始设备发现
   */
  public async startDiscovery(): Promise<boolean> {
    try {
      // 清空已发现的设备
      this.discoveredDevices.clear();
      
      await bluetooth.startBluetoothDiscovery();
      console.info('[经典蓝牙] 开始发现设备');
      return true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[经典蓝牙] 发现失败: ${err.message}`);
      return false;
    }
  }

  /**
   * 停止设备发现
   */
  public async stopDiscovery(): Promise<boolean> {
    try {
      await bluetooth.stopBluetoothDiscovery();
      console.info('[经典蓝牙] 停止发现');
      return true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[经典蓝牙] 停止失败: ${err.message}`);
      return false;
    }
  }

  /**
   * 获取已发现的设备列表
   */
  public getDiscoveredDevices(): Array<ClassicBluetoothDevice> {
    return Array.from(this.discoveredDevices.values());
  }

  /**
   * 获取已配对的设备列表
   */
  public async getPairedDevices(): Promise<Array<ClassicBluetoothDevice>> {
    try {
      const devices = await bluetooth.getPairedDevices();
      return devices.map((device) => ({
        deviceId: device.deviceId,
        deviceName: device.deviceName || 'Unknown',
        deviceClass: device.deviceClass,
        bondState: bluetooth.BondState.BOND_STATE_BONDED
      }));
    } catch (error) {
      console.error('[经典蓝牙] 获取配对设备失败:', error);
      return [];
    }
  }

  /**
   * 发起配对
   */
  public async pairDevice(deviceId: string): Promise<boolean> {
    try {
      await bluetooth.pairDevice(deviceId);
      console.info(`[经典蓝牙] 正在配对 ${deviceId}`);
      return true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[经典蓝牙] 配对失败: ${err.message}`);
      return false;
    }
  }

  /**
   * 取消配对
   */
  public async unpairDevice(deviceId: string): Promise<boolean> {
    try {
      await bluetooth.unpairDevice(deviceId);
      console.info(`[经典蓝牙] 已取消配对 ${deviceId}`);
      return true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[经典蓝牙] 取消配对失败: ${err.message}`);
      return false;
    }
  }

  /**
   * 创建RFCOMM服务端
   * @param serviceName 服务名称
   * @param uuid 服务UUID
   */
  public async createRfcommServer(
    serviceName: string,
    uuid: string
  ): Promise<number | null> {
    try {
      // 创建Socket
      this.serverSocket = bluetooth.createBluetoothSocket(
        bluetooth.SocketType.SOCKET_RFCOMM
      );

      // 监听连接
      this.serverSocket.on('connection', (client: bluetooth.BluetoothSocket) => {
        console.info('[经典蓝牙] 客户端已连接');
        this.clientSocket = client;
        this.setupDataHandler(client);
        
        if (this.onConnectionChanged) {
          this.onConnectionChanged(3); // CONNECTED
        }
      });

      // 绑定并监听
      const channel = await this.serverSocket.listen({
        uuid: uuid,
        name: serviceName
      });

      console.info(`[经典蓝牙] 服务端已创建,通道: ${channel}`);
      return channel;
    } catch (error) {
      console.error('[经典蓝牙] 创建服务端失败:', error);
      return null;
    }
  }

  /**
   * 连接到RFCOMM服务端
   * @param deviceId 设备地址
   * @param uuid 服务UUID
   */
  public async connectRfcomm(
    deviceId: string,
    uuid: string
  ): Promise<boolean> {
    try {
      // 创建Socket
      this.clientSocket = bluetooth.createBluetoothSocket(
        bluetooth.SocketType.SOCKET_RFCOMM
      );

      // 设置事件处理
      this.setupDataHandler(this.clientSocket);

      // 连接
      await this.clientSocket.connect({
        deviceId: deviceId,
        uuid: uuid
      });

      console.info(`[经典蓝牙] 已连接到 ${deviceId}`);
      
      if (this.onConnectionChanged) {
        this.onConnectionChanged(3); // CONNECTED
      }
      
      return true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[经典蓝牙] 连接失败: ${err.message}`);
      return false;
    }
  }

  /**
   * 设置数据处理器
   */
  private setupDataHandler(socket: bluetooth.BluetoothSocket): void {
    socket.on('data', (data: ArrayBuffer) => {
      if (this.onDataReceived) {
        this.onDataReceived(data);
      }
    });

    socket.on('error', (err: Error) => {
      console.error('[经典蓝牙] Socket错误:', err);
      if (this.onConnectionChanged) {
        this.onConnectionChanged(4); // ERROR
      }
    });

    socket.on('close', () => {
      console.info('[经典蓝牙] Socket已关闭');
      if (this.onConnectionChanged) {
        this.onConnectionChanged(0); // CLOSED
      }
    });
  }

  /**
   * 发送数据
   */
  public async sendData(data: ArrayBuffer): Promise<boolean> {
    if (!this.clientSocket) {
      console.error('[经典蓝牙] 未连接');
      return false;
    }

    try {
      await this.clientSocket.send(data);
      return true;
    } catch (error) {
      console.error('[经典蓝牙] 发送失败:', error);
      return false;
    }
  }

  /**
   * 发送字符串
   */
  public async sendString(text: string): Promise<boolean> {
    const encoder = new TextEncoder();
    const data = encoder.encode(text).buffer;
    return await this.sendData(data);
  }

  /**
   * 断开连接
   */
  public async disconnect(): Promise<void> {
    if (this.clientSocket) {
      await this.clientSocket.close();
      this.clientSocket = null;
    }
    
    if (this.serverSocket) {
      await this.serverSocket.close();
      this.serverSocket = null;
    }
    
    console.info('[经典蓝牙] 已断开');
  }

  /**
   * 设置回调函数
   */
  public setOnDeviceFound(callback: (device: ClassicBluetoothDevice) => void): void {
    this.onDeviceFound = callback;
  }

  public setOnBondStateChanged(callback: (device: ClassicBluetoothDevice) => void): void {
    this.onBondStateChanged = callback;
  }

  public setOnConnectionChanged(callback: (state: number) => void): void {
    this.onConnectionChanged = callback;
  }

  public setOnDataReceived(callback: (data: ArrayBuffer) => void): void {
    this.onDataReceived = callback;
  }

  /**
   * 移除所有监听
   */
  public removeAllListeners(): void {
    bluetooth.off('bluetoothDeviceFind');
    bluetooth.off('bondStateChange');
    bluetooth.off('stateChange');
    
    if (this.clientSocket) {
      this.clientSocket.off('data');
      this.clientSocket.off('error');
      this.clientSocket.off('close');
    }
    
    if (this.serverSocket) {
      this.serverSocket.off('connection');
    }
    
    console.info('[经典蓝牙] 已移除所有监听');
  }
}

3.2 RFCOMM聊天应用示例

import { ClassicBluetoothManager, ClassicBluetoothDevice } from './ClassicBluetoothManager';

/**
 * RFCOMM聊天应用
 * 两台设备间通过经典蓝牙聊天
 */
@Entry
@Component
struct BluetoothChatPage {
  private btManager: ClassicBluetoothManager = ClassicBluetoothManager.getInstance();
  
  // 聊天服务UUID
  private readonly CHAT_SERVICE_UUID = '00001101-0000-1000-8000-00805F9B34FB';
  
  @State isBluetoothEnabled: boolean = false;
  @State isDiscovering: boolean = false;
  @State discoveredDevices: Array<ClassicBluetoothDevice> = [];
  @State pairedDevices: Array<ClassicBluetoothDevice> = [];
  @State isConnected: boolean = false;
  @State connectedDeviceName: string = '';
  @State messages: Array<{ text: string, isMine: boolean }> = [];
  @State inputMessage: string = '';
  @State isServer: boolean = false;

  aboutToAppear(): void {
    this.checkBluetoothState();
    this.loadPairedDevices();
    this.registerCallbacks();
  }

  aboutToDisappear(): void {
    this.btManager.disconnect();
    this.btManager.removeAllListeners();
  }

  /**
   * 检查蓝牙状态
   */
  async checkBluetoothState(): Promise<void> {
    this.isBluetoothEnabled = await this.btManager.isBluetoothEnabled();
  }

  /**
   * 加载已配对设备
   */
  async loadPairedDevices(): Promise<void> {
    this.pairedDevices = await this.btManager.getPairedDevices();
  }

  /**
   * 注册回调
   */
  registerCallbacks(): void {
    // 设备发现回调
    this.btManager.setOnDeviceFound((device: ClassicBluetoothDevice) => {
      const index = this.discoveredDevices.findIndex(d => d.deviceId === device.deviceId);
      if (index >= 0) {
        this.discoveredDevices[index] = device;
      } else {
        this.discoveredDevices.push(device);
      }
    });

    // 配对状态回调
    this.btManager.setOnBondStateChanged((device: ClassicBluetoothDevice) => {
      this.loadPairedDevices();
    });

    // 连接状态回调
    this.btManager.setOnConnectionChanged((state: number) => {
      this.isConnected = state === 3;
      if (!this.isConnected) {
        this.connectedDeviceName = '';
      }
    });

    // 数据接收回调
    this.btManager.setOnDataReceived((data: ArrayBuffer) => {
      const decoder = new TextDecoder();
      const text = decoder.decode(data);
      this.messages.push({ text: text, isMine: false });
    });
  }

  /**
   * 开启蓝牙
   */
  async enableBluetooth(): Promise<void> {
    const success = await this.btManager.enableBluetooth();
    if (success) {
      this.isBluetoothEnabled = true;
    }
  }

  /**
   * 开始发现设备
   */
  async startDiscovery(): Promise<void> {
    this.isDiscovering = true;
    this.discoveredDevices = [];
    
    const success = await this.btManager.startDiscovery();
    if (!success) {
      this.isDiscovering = false;
    }

    // 设置超时
    setTimeout(() => {
      if (this.isDiscovering) {
        this.stopDiscovery();
      }
    }, 60000);
  }

  /**
   * 停止发现
   */
  async stopDiscovery(): Promise<void> {
    await this.btManager.stopDiscovery();
    this.isDiscovering = false;
  }

  /**
   * 创建聊天服务(作为服务端)
   */
  async createChatServer(): Promise<void> {
    const channel = await this.btManager.createRfcommServer(
      'BluetoothChat',
      this.CHAT_SERVICE_UUID
    );
    
    if (channel !== null) {
      this.isServer = true;
      console.info(`[聊天] 服务已创建,通道: ${channel}`);
    }
  }

  /**
   * 连接到设备(作为客户端)
   */
  async connectToDevice(device: ClassicBluetoothDevice): Promise<void> {
    await this.stopDiscovery();
    
    // 如果未配对,先配对
    if (device.bondState !== bluetooth.BondState.BOND_STATE_BONDED) {
      const paired = await this.btManager.pairDevice(device.deviceId);
      if (!paired) {
        console.error('[聊天] 配对失败');
        return;
      }
    }
    
    // 连接
    const connected = await this.btManager.connectRfcomm(
      device.deviceId,
      this.CHAT_SERVICE_UUID
    );
    
    if (connected) {
      this.connectedDeviceName = device.deviceName;
    }
  }

  /**
   * 发送消息
   */
  async sendMessage(): Promise<void> {
    if (!this.inputMessage || !this.isConnected) {
      return;
    }

    const success = await this.btManager.sendString(this.inputMessage);
    if (success) {
      this.messages.push({ text: this.inputMessage, isMine: true });
      this.inputMessage = '';
    }
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('蓝牙聊天')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')

        Blank()

        Toggle({ type: ToggleType.Switch, isOn: this.isBluetoothEnabled })
          .onChange((isOn: boolean) => {
            if (isOn) {
              this.enableBluetooth();
            } else {
              this.btManager.disableBluetooth();
              this.isBluetoothEnabled = false;
            }
          })
      }
      .width('100%')
      .padding(20)
      .backgroundColor('#1A1A2E')

      if (this.isBluetoothEnabled) {
        if (this.isConnected) {
          // 聊天界面
          this.ChatView()
        } else {
          // 连接界面
          this.ConnectionView()
        }
      } else {
        // 提示
        Column() {
          Text('蓝牙未开启')
            .fontSize(18)
            .fontColor('#AAAAAA')
            .margin({ top: 100 })
        }
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0F0F1A')
  }

  /**
   * 连接界面
   */
  @Builder
  ConnectionView() {
    Column() {
      // 创建服务按钮
      Button('创建聊天服务')
        .width('90%')
        .height(50)
        .backgroundColor('#4A90E2')
        .onClick(() => {
          this.createChatServer();
        })
        .margin({ top: 20 })

      // 发现设备按钮
      Button(this.isDiscovering ? '发现中...' : '发现设备')
        .width('90%')
        .height(50)
        .backgroundColor(this.isDiscovering ? '#666666' : '#4CAF50')
        .enabled(!this.isDiscovering)
        .onClick(() => {
          this.startDiscovery();
        })
        .margin({ top: 15 })

      // 已配对设备
      if (this.pairedDevices.length > 0) {
        Column() {
          Text('已配对设备')
            .fontSize(16)
            .fontColor('#FFFFFF')
            .width('100%')
            .margin({ bottom: 10 })

          ForEach(this.pairedDevices, (device: ClassicBluetoothDevice) => {
            Row() {
              Text(device.deviceName)
                .fontSize(14)
                .fontColor('#FFFFFF')
                .layoutWeight(1)

              Button('连接')
                .width(70)
                .height(35)
                .backgroundColor('#4A90E2')
                .onClick(() => {
                  this.connectToDevice(device);
                })
            }
            .width('100%')
            .padding(10)
            .backgroundColor('#16213E')
            .borderRadius(8)
            .margin({ bottom: 8 })
          })
        }
        .width('90%')
        .padding(15)
        .backgroundColor('#1A1A2E')
        .borderRadius(12)
        .margin({ top: 20 })
      }

      // 发现的设备
      if (this.discoveredDevices.length > 0) {
        Column() {
          Text(`发现 ${this.discoveredDevices.length} 台设备`)
            .fontSize(16)
            .fontColor('#FFFFFF')
            .width('100%')
            .margin({ bottom: 10 })

          List() {
            ForEach(this.discoveredDevices, (device: ClassicBluetoothDevice) => {
              ListItem() {
                Row() {
                  Column() {
                    Text(device.deviceName)
                      .fontSize(14)
                      .fontColor('#FFFFFF')

                    Text(device.deviceId.substring(0, 8) + '...')
                      .fontSize(12)
                      .fontColor('#AAAAAA')
                  }
                  .alignItems(HorizontalAlign.Start)
                  .layoutWeight(1)

                  Button('连接')
                    .width(70)
                    .height(35)
                    .backgroundColor('#4CAF50')
                    .onClick(() => {
                      this.connectToDevice(device);
                    })
                }
                .width('100%')
                .padding(10)
                .backgroundColor('#16213E')
                .borderRadius(8)
              }
            }, (device: ClassicBluetoothDevice) => device.deviceId)
          }
          .width('100%')
          .height('40%')
        }
        .width('90%')
        .padding(15)
        .backgroundColor('#1A1A2E')
        .borderRadius(12)
        .margin({ top: 20 })
      }
    }
    .width('100%')
    .layoutWeight(1)
  }

  /**
   * 聊天界面
   */
  @Builder
  ChatView() {
    Column() {
      // 连接信息
      Row() {
        Text(this.isServer ? '服务端' : '客户端')
          .fontSize(14)
          .fontColor('#AAAAAA')

        Text(this.connectedDeviceName)
          .fontSize(14)
          .fontColor('#FFFFFF')
          .margin({ left: 10 })

        Blank()

        Button('断开')
          .fontSize(12)
          .height(30)
          .backgroundColor('#FF5722')
          .onClick(() => {
            this.btManager.disconnect();
          })
      }
      .width('90%')
      .padding(10)
      .backgroundColor('#16213E')
      .borderRadius(8)
      .margin({ top: 20 })

      // 消息列表
      List() {
        ForEach(this.messages, (msg: { text: string, isMine: boolean }, index: number) => {
          ListItem() {
            Row() {
              if (msg.isMine) {
                Blank()
              }

              Text(msg.text)
                .fontSize(14)
                .fontColor('#FFFFFF')
                .padding(10)
                .backgroundColor(msg.isMine ? '#4A90E2' : '#4CAF50')
                .borderRadius(12)
                .maxWidth('70%')

              if (!msg.isMine) {
                Blank()
              }
            }
            .width('100%')
            .justifyContent(msg.isMine ? FlexAlign.End : FlexAlign.Start)
          }
          .margin({ bottom: 10 })
        })
      }
      .width('90%')
      .layoutWeight(1)
      .margin({ top: 20 })

      // 输入区域
      Row() {
        TextInput({ text: $$this.inputMessage, placeholder: '输入消息' })
          .width('70%')
          .height(45)
          .backgroundColor('#16213E')
          .fontColor('#FFFFFF')

        Button('发送')
          .width('25%')
          .height(45)
          .backgroundColor('#4A90E2')
          .onClick(() => {
            this.sendMessage();
          })
      }
      .width('90%')
      .justifyContent(FlexAlign.SpaceBetween)
      .margin({ top: 15, bottom: 20 })
    }
    .width('100%')
    .layoutWeight(1)
  }
}

3.3 蓝牙打印机示例

import { ClassicBluetoothManager } from './ClassicBluetoothManager';

/**
 * 蓝牙打印机控制器
 * 通过RFCOMM发送打印指令
 */
export class BluetoothPrinter {
  private btManager: ClassicBluetoothManager = ClassicBluetoothManager.getInstance();
  
  // 串口服务UUID(SPP)
  private readonly SPP_UUID = '00001101-0000-1000-8000-00805F9B34FB';
  
  // 打印机设备地址
  private printerAddress: string = '';

  /**
   * 连接打印机
   */
  public async connect(address: string): Promise<boolean> {
    this.printerAddress = address;
    return await this.btManager.connectRfcomm(address, this.SPP_UUID);
  }

  /**
   * 断开连接
   */
  public async disconnect(): Promise<void> {
    await this.btManager.disconnect();
  }

  /**
   * 打印文本
   */
  public async printText(text: string): Promise<boolean> {
    // ESC/POS指令
    const commands: number[] = [];
    
    // 初始化打印机
    commands.push(0x1B, 0x40);
    
    // 设置字体大小
    commands.push(0x1D, 0x21, 0x00); // 正常大小
    
    // 打印文本
    for (let i = 0; i < text.length; i++) {
      commands.push(text.charCodeAt(i));
    }
    
    // 换行
    commands.push(0x0A);
    
    return await this.sendCommands(commands);
  }

  /**
   * 打印条形码
   */
  public async printBarcode(data: string, type: number = 0x41): Promise<boolean> {
    const commands: number[] = [];
    
    // 初始化
    commands.push(0x1B, 0x40);
    
    // 设置条形码高度
    commands.push(0x1D, 0x68, 0x50);
    
    // 设置条形码宽度
    commands.push(0x1D, 0x77, 0x02);
    
    // 打印条形码
    commands.push(0x1D, 0x6B, type, data.length);
    
    // 条形码数据
    for (let i = 0; i < data.length; i++) {
      commands.push(data.charCodeAt(i));
    }
    
    return await this.sendCommands(commands);
  }

  /**
   * 打印二维码
   */
  public async printQRCode(data: string): Promise<boolean> {
    const commands: number[] = [];
    
    // 初始化
    commands.push(0x1B, 0x40);
    
    // 设置二维码模型
    commands.push(0x1D, 0x28, 0x6B, 0x04, 0x00, 0x31, 0x41, 0x32, 0x00);
    
    // 设置二维码大小
    commands.push(0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x43, 0x08);
    
    // 设置纠错等级
    commands.push(0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x45, 0x31);
    
    // 发送数据
    const dataLen = data.length + 3;
    commands.push(0x1D, 0x28, 0x6B, dataLen & 0xFF, (dataLen >> 8) & 0xFF, 0x31, 0x50, 0x30);
    
    for (let i = 0; i < data.length; i++) {
      commands.push(data.charCodeAt(i));
    }
    
    // 打印二维码
    commands.push(0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30);
    
    return await this.sendCommands(commands);
  }

  /**
   * 切纸
   */
  public async cutPaper(): Promise<boolean> {
    const commands: number[] = [
      0x1D, 0x56, 0x00  // 全切
    ];
    return await this.sendCommands(commands);
  }

  /**
   * 发送指令
   */
  private async sendCommands(commands: number[]): Promise<boolean> {
    const buffer = new ArrayBuffer(commands.length);
    const view = new Uint8Array(buffer);
    
    for (let i = 0; i < commands.length; i++) {
      view[i] = commands[i];
    }
    
    return await this.btManager.sendData(buffer);
  }
}

四、踩坑与注意事项

4.1 权限配置

// module.json5
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.USE_BLUETOOTH",
        "reason": "使用蓝牙功能"
      },
      {
        "name": "ohos.permission.DISCOVER_BLUETOOTH",
        "reason": "发现蓝牙设备"
      },
      {
        "name": "ohos.permission.MANAGE_BLUETOOTH",
        "reason": "管理蓝牙设置"
      },
      {
        "name": "ohos.permission.LOCATION",
        "reason": "蓝牙扫描需要位置权限"
      }
    ]
  }
}

4.2 配对流程处理

坑点:配对可能需要用户确认PIN码。

/**
 * 监听配对请求
 */
bluetooth.on('pinRequest', (data: { deviceId: string, pinCode: string }) => {
  console.info(`[配对] 设备 ${data.deviceId} 请求配对,PIN: ${data.pinCode}`);
  
  // 自动确认(谨慎使用)
  bluetooth.pairDeviceConfirm(data.deviceId, true);
});

// 监听配对确认请求
bluetooth.on('bondStateChange', (data: { deviceId: string, bondState: bluetooth.BondState }) => {
  if (data.bondState === bluetooth.BondState.BOND_STATE_BONDING) {
    console.info('[配对] 正在配对...');
  } else if (data.bondState === bluetooth.BondState.BOND_STATE_BONDED) {
    console.info('[配对] 配对成功');
  } else if (data.bondState === bluetooth.BondState.BOND_STATE_NONE) {
    console.info('[配对] 配对失败');
  }
});

4.3 连接超时处理

/**
 * 带超时的RFCOMM连接
 */
async function connectWithTimeout(
  deviceId: string,
  uuid: string,
  timeout: number = 20000
): Promise<boolean> {
  return new Promise(async (resolve) => {
    let resolved = false;
    
    const timer = setTimeout(() => {
      if (!resolved) {
        resolved = true;
        resolve(false);
        console.warn('[RFCOMM] 连接超时');
      }
    }, timeout);

    try {
      const success = await btManager.connectRfcomm(deviceId, uuid);
      if (!resolved) {
        resolved = true;
        clearTimeout(timer);
        resolve(success);
      }
    } catch (error) {
      if (!resolved) {
        resolved = true;
        clearTimeout(timer);
        resolve(false);
      }
    }
  });
}

4.4 大数据分块发送

坑点:RFCOMM有MTU限制,大数据需要分块。

/**
 * 分块发送大数据
 */
async function sendDataInChunks(
  data: ArrayBuffer,
  chunkSize: number = 1024
): Promise<boolean> {
  const totalLength = data.byteLength;
  const chunkCount = Math.ceil(totalLength / chunkSize);
  
  for (let i = 0; i < chunkCount; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, totalLength);
    const chunk = data.slice(start, end);
    
    const success = await btManager.sendData(chunk);
    if (!success) {
      console.error(`[RFCOMM] 发送第 ${i + 1} 块失败`);
      return false;
    }
    
    // 等待确认
    await delay(50);
  }
  
  console.info(`[RFCOMM] 发送完成,共 ${chunkCount}`);
  return true;
}

function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

4.5 设备兼容性

坑点:不同设备的UUID可能不同。

/**
 * 常见服务UUID
 */
const COMMON_UUIDS = {
  // 串口服务(SPP)
  SPP: '00001101-0000-1000-8000-00805F9B34FB',
  
  // 音频网关
  HSP_AG: '00001112-0000-1000-8000-00805F9B34FB',
  
  // 免提
  HFP_AG: '0000111F-0000-1000-8000-00805F9B34FB',
  
  // 文件传输
  FTP: '00001106-0000-1000-8000-00805F9B34FB',
  
  // 对象推送
  OPP: '00001105-0000-1000-8000-00805F9B34FB'
};

/**
 * 尝试连接多个UUID
 */
async function tryConnectMultipleUuids(
  deviceId: string,
  uuids: string[]
): Promise<boolean> {
  for (const uuid of uuids) {
    try {
      const success = await btManager.connectRfcomm(deviceId, uuid);
      if (success) {
        console.info(`[RFCOMM] 使用UUID ${uuid} 连接成功`);
        return true;
      }
    } catch (error) {
      console.warn(`[RFCOMM] UUID ${uuid} 连接失败`);
    }
  }
  
  return false;
}

五、HarmonyOS 6适配

5.1 API变更

API HarmonyOS 5 HarmonyOS 6 说明
createBluetoothSocket() 同步创建 异步创建 统一异步模式
connect() 基础功能 支持超时设置 新增timeout参数
新增 - getSocketInfo() 获取Socket详细信息

适配代码

/**
 * HarmonyOS 6 RFCOMM适配
 */
async function createSocketAdaptive(): Promise<bluetooth.BluetoothSocket | null> {
  const apiVersion = getApiVersion();

  try {
    if (apiVersion >= 6) {
      // HarmonyOS 6
      const socket = await bluetooth.createBluetoothSocket(
        bluetooth.SocketType.SOCKET_RFCOMM
      );
      return socket;
    } else {
      // HarmonyOS 5
      const socket = bluetooth.createBluetoothSocket(
        bluetooth.SocketType.SOCKET_RFCOMM
      );
      return socket;
    }
  } catch (error) {
    console.error('[RFCOMM] 创建Socket失败:', error);
    return null;
  }
}

5.2 新增功能

HarmonyOS 6增强了经典蓝牙能力:

// 1. 获取Socket统计信息
const socketInfo = await socket.getSocketInfo();
console.info(`发送: ${socketInfo.bytesSent} 字节`);
console.info(`接收: ${socketInfo.bytesReceived} 字节`);

// 2. 设置Socket选项
await socket.setSocketOption({
  sendBufferSize: 8192,
  receiveBufferSize: 8192,
  keepAlive: true,
  noDelay: true
});

// 3. 蓝牙设备电量查询
const batteryLevel = await bluetooth.getDeviceBattery(deviceId);
console.info(`设备电量: ${batteryLevel}%`);

// 4. 连接质量评估
const linkQuality = await bluetooth.getLinkQuality(deviceId);
console.info(`连接质量: ${linkQuality}`);

5.3 性能优化

// 1. 调整传输功率
await bluetooth.setTransmitPowerLevel(deviceId, 'high');

// 2. 设置链路超时
await bluetooth.setLinkTimeout(deviceId, 30000); // 30秒

// 3. 启用数据压缩(如果设备支持)
await socket.enableCompression(true);

六、总结

经典蓝牙的RFCOMM协议为设备间的流式数据传输提供了可靠的解决方案。本文全面讲解了鸿蒙系统中经典蓝牙开发的核心要点:

核心要点回顾

  1. 设备发现与配对:理解配对流程,处理用户交互
  2. RFCOMM服务端:创建服务、注册SDP记录、等待连接
  3. RFCOMM客户端:发现服务、发起连接、数据通信
  4. 数据传输:处理MTU限制、分块发送、流控管理
  5. 设备兼容性:处理不同设备的UUID差异

最佳实践建议

  • 封装统一的蓝牙管理类,简化使用
  • 实现完善的配对和连接错误处理
  • 使用操作队列避免并发问题
  • 做好设备兼容性测试

下一步学习

  • NFC标签读写(下一篇文章)
  • 蓝牙音频传输
  • 多设备同时连接
  • 蓝牙安全机制

经典蓝牙虽然"老",但在音频传输、串口通信等场景下依然不可替代。希望本文能帮助你掌握RFCOMM开发的核心技能!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。