蓝牙GATT:HarmonyOS APP开发服务与特征值操作

举报
Jack20 发表于 2026/06/19 20:13:39 2026/06/19
【摘要】 蓝牙GATT:HarmonyOS APP开发服务与特征值操作上一篇我们聊了BLE的扫描与连接,但连接只是第一步——真正有价值的是连接后的数据交互。这就涉及到GATT(Generic Attribute Profile)了,它是BLE数据交换的核心协议。今天咱们就深入聊聊GATT的服务与特征值操作,看看如何优雅地读写数据、订阅通知,实现设备与手机的"对话"。 一、背景与动机 1.1 GATT...

蓝牙GATT:HarmonyOS APP开发服务与特征值操作

上一篇我们聊了BLE的扫描与连接,但连接只是第一步——真正有价值的是连接后的数据交互。这就涉及到GATT(Generic Attribute Profile)了,它是BLE数据交换的核心协议。今天咱们就深入聊聊GATT的服务与特征值操作,看看如何优雅地读写数据、订阅通知,实现设备与手机的"对话"。

一、背景与动机

1.1 GATT架构详解

GATT定义了BLE设备间数据交换的层次结构,理解这个架构是BLE开发的基础:
图片.png

核心概念解释

概念 说明 类比
Profile 规范定义,如心率Profile 一类应用的标准
Service 服务,一组相关数据 一个功能模块
Characteristic 特征值,具体数据项 一个数据变量
Descriptor 描述符,描述特征值属性 变量的属性
UUID 唯一标识符 变量的地址

1.2 标准UUID与自定义UUID

BLE定义了一系列标准UUID,用于常见应用:

// 常用标准UUID
const STANDARD_UUIDS = {
  // 通用访问服务
  GAP_SERVICE: '00001800-0000-1000-8000-00805F9B34FB',
  DEVICE_NAME: '00002A00-0000-1000-8000-00805F9B34FB',
  
  // 通用属性服务
  GATT_SERVICE: '00001801-0000-1000-8000-00805F9B34FB',
  
  // 心率服务
  HEART_RATE_SERVICE: '0000180D-0000-1000-8000-00805F9B34FB',
  HEART_RATE_MEASUREMENT: '00002A37-0000-1000-8000-00805F9B34FB',
  
  // 血压服务
  BLOOD_PRESSURE_SERVICE: '00001813-0000-1000-8000-00805F9B34FB',
  
  // 电池服务
  BATTERY_SERVICE: '0000180F-0000-1000-8000-00805F9B34FB',
  BATTERY_LEVEL: '00002A19-0000-1000-8000-00805F9B34FB',
  
  // 设备信息服务
  DEVICE_INFO_SERVICE: '0000180A-0000-1000-8000-00805F9B34FB',
  MANUFACTURER_NAME: '00002A29-0000-1000-8000-00805F9B34FB',
  MODEL_NUMBER: '00002A24-0000-1000-8000-00805F9B34FB',
  FIRMWARE_REVISION: '00002A26-0000-1000-8000-00805F9B34FB'
};

// 自定义UUID示例(厂商自定义)
const CUSTOM_UUIDS = {
  // 智能灯服务
  LIGHT_SERVICE: '0000FFF0-0000-1000-8000-00805F9B34FB',
  LIGHT_SWITCH: '0000FFF1-0000-1000-8000-00805F9B34FB',
  LIGHT_BRIGHTNESS: '0000FFF2-0000-1000-8000-00805F9B34FB',
  LIGHT_COLOR: '0000FFF3-0000-1000-8000-00805F9B34FB',
  
  // 温湿度传感器服务
  TH_SENSOR_SERVICE: '0000FFE0-0000-1000-8000-00805F9B34FB',
  TEMPERATURE: '0000FFE1-0000-1000-8000-00805F9B34FB',
  HUMIDITY: '0000FFE2-0000-1000-8000-00805F9B34FB'
};

二、核心原理

2.1 特征值属性

每个特征值都有一组属性,决定了它可以被如何操作:

// 特征值属性
const CHARACTERISTIC_PROPERTIES = {
  // 可读
  READ: 0x02,
  
  // 可写(无需响应)
  WRITE_NO_RESPONSE: 0x04,
  
  // 可写(需要响应)
  WRITE: 0x08,
  
  // 可通知
  NOTIFY: 0x10,
  
  // 可指示(需要确认)
  INDICATE: 0x20,
  
  // 广播
  BROADCAST: 0x01,
  
  // 扩展属性
  EXTENDED_PROPERTIES: 0x80
};

/**
 * 检查特征值是否支持某属性
 */
function hasProperty(properties: number, flag: number): boolean {
  return (properties & flag) !== 0;
}

// 使用示例
const charProperties = 0x1A; // 通知 + 写 + 读
console.info(`可读: ${hasProperty(charProperties, CHARACTERISTIC_PROPERTIES.READ)}`);
console.info(`可通知: ${hasProperty(charProperties, CHARACTERISTIC_PROPERTIES.NOTIFY)}`);

2.2 数据操作流程

图片.png

2.3 CCCD(Client Characteristic Configuration Descriptor)

CCCD是开启/关闭通知的关键描述符:

// CCCD UUID
const CCCD_UUID = '00002902-0000-1000-8000-00805F9B34FB';

// CCCD值
const CCCD_VALUES = {
  DISABLE: 0x0000,      // 禁用
  NOTIFICATION: 0x0001, // 启用通知
  INDICATION: 0x0002    // 启用指示
};

三、代码实战

3.1 GATT服务发现与解析

import bluetooth from '@ohos.bluetooth';
import { BleManager } from './BleManager';

/**
 * GATT服务管理器
 * 封装服务发现、特征值操作等功能
 */
export class GattServiceManager {
  private bleManager: BleManager = BleManager.getInstance();
  private gattClient: bluetooth.GattClient | null = null;
  
  // 服务缓存
  private services: Map<string, bluetooth.GattService> = new Map();
  
  // 特征值缓存
  private characteristics: Map<string, bluetooth.GattCharacteristic> = new Map();

  /**
   * 初始化GATT服务管理
   * 连接成功后调用
   */
  public async init(): Promise<boolean> {
    try {
      // 发现所有服务
      const serviceList = await this.discoverAllServices();
      if (!serviceList) {
        return false;
      }

      // 缓存服务和特征值
      serviceList.forEach((service) => {
        this.services.set(service.serviceUuid, service);
        
        service.characteristics.forEach((char) => {
          const key = `${service.serviceUuid}:${char.characteristicUuid}`;
          this.characteristics.set(key, char);
        });
      });

      console.info(`[GATT] 发现 ${this.services.size} 个服务`);
      console.info(`[GATT] 发现 ${this.characteristics.size} 个特征值`);
      
      return true;
    } catch (error) {
      console.error('[GATT] 初始化失败:', error);
      return false;
    }
  }

  /**
   * 发现所有GATT服务
   */
  private async discoverAllServices(): Promise<Array<bluetooth.GattService> | null> {
    const services = await this.bleManager.discoverServices();
    return services;
  }

  /**
   * 获取所有服务
   */
  public getAllServices(): Array<bluetooth.GattService> {
    return Array.from(this.services.values());
  }

  /**
   * 获取指定服务
   */
  public getService(serviceUuid: string): bluetooth.GattService | undefined {
    return this.services.get(serviceUuid);
  }

  /**
   * 获取服务的所有特征值
   */
  public getCharacteristics(serviceUuid: string): Array<bluetooth.GattCharacteristic> {
    const service = this.services.get(serviceUuid);
    return service ? service.characteristics : [];
  }

  /**
   * 获取指定特征值
   */
  public getCharacteristic(
    serviceUuid: string,
    characteristicUuid: string
  ): bluetooth.GattCharacteristic | undefined {
    const key = `${serviceUuid}:${characteristicUuid}`;
    return this.characteristics.get(key);
  }

  /**
   * 打印服务结构(调试用)
   */
  public printServiceStructure(): void {
    console.info('=== GATT服务结构 ===');
    
    this.services.forEach((service, serviceUuid) => {
      console.info(`服务: ${serviceUuid}`);
      
      service.characteristics.forEach((char) => {
        const props = this.parseProperties(char.properties);
        console.info(`  特征值: ${char.characteristicUuid}`);
        console.info(`    属性: ${props.join(', ')}`);
        
        if (char.descriptors) {
          char.descriptors.forEach((desc) => {
            console.info(`    描述符: ${desc.descriptorUuid}`);
          });
        }
      });
    });
  }

  /**
   * 解析特征值属性
   */
  private parseProperties(properties: number): string[] {
    const props: string[] = [];
    
    if (properties & 0x01) props.push('Broadcast');
    if (properties & 0x02) props.push('Read');
    if (properties & 0x04) props.push('WriteNoResponse');
    if (properties & 0x08) props.push('Write');
    if (properties & 0x10) props.push('Notify');
    if (properties & 0x20) props.push('Indicate');
    if (properties & 0x80) props.push('ExtendedProperties');
    
    return props;
  }

  /**
   * 检查特征值是否支持读
   */
  public isReadable(char: bluetooth.GattCharacteristic): boolean {
    return (char.properties & 0x02) !== 0;
  }

  /**
   * 检查特征值是否支持写
   */
  public isWritable(char: bluetooth.GattCharacteristic): boolean {
    return (char.properties & 0x08) !== 0;
  }

  /**
   * 检查特征值是否支持通知
   */
  public isNotifiable(char: bluetooth.GattCharacteristic): boolean {
    return (char.properties & 0x10) !== 0;
  }

  /**
   * 清空缓存
   */
  public clear(): void {
    this.services.clear();
    this.characteristics.clear();
  }
}

3.2 特征值读写操作封装

import bluetooth from '@ohos.bluetooth';
import { BleManager } from './BleManager';

/**
 * GATT数据操作器
 * 封装特征值的读写、通知等操作
 */
export class GattDataOperator {
  private bleManager: BleManager = BleManager.getInstance();
  
  // 通知回调映射
  private notificationCallbacks: Map<string, (data: ArrayBuffer) => void> = new Map();

  /**
   * 读取特征值(返回原始数据)
   */
  public async readRaw(
    serviceUuid: string,
    characteristicUuid: string
  ): Promise<ArrayBuffer | null> {
    return await this.bleManager.readCharacteristic(serviceUuid, characteristicUuid);
  }

  /**
   * 读取特征值(返回字符串)
   */
  public async readString(
    serviceUuid: string,
    characteristicUuid: string
  ): Promise<string | null> {
    const data = await this.readRaw(serviceUuid, characteristicUuid);
    if (!data) return null;
    
    return this.arrayBufferToString(data);
  }

  /**
   * 读取特征值(返回数字)
   */
  public async readNumber(
    serviceUuid: string,
    characteristicUuid: string,
    bitLength: 8 | 16 | 32 = 8,
    signed: boolean = false
  ): Promise<number | null> {
    const data = await this.readRaw(serviceUuid, characteristicUuid);
    if (!data) return null;
    
    return this.arrayBufferToNumber(data, bitLength, signed);
  }

  /**
   * 写入字符串
   */
  public async writeString(
    serviceUuid: string,
    characteristicUuid: string,
    value: string
  ): Promise<boolean> {
    const data = this.stringToArrayBuffer(value);
    return await this.bleManager.writeCharacteristic(
      serviceUuid,
      characteristicUuid,
      data
    );
  }

  /**
   * 写入数字
   */
  public async writeNumber(
    serviceUuid: string,
    characteristicUuid: string,
    value: number,
    bitLength: 8 | 16 | 32 = 8
  ): Promise<boolean> {
    const data = this.numberToArrayBuffer(value, bitLength);
    return await this.bleManager.writeCharacteristic(
      serviceUuid,
      characteristicUuid,
      data
    );
  }

  /**
   * 写入字节数组
   */
  public async writeBytes(
    serviceUuid: string,
    characteristicUuid: string,
    bytes: number[]
  ): Promise<boolean> {
    const data = new ArrayBuffer(bytes.length);
    const view = new Uint8Array(data);
    for (let i = 0; i < bytes.length; i++) {
      view[i] = bytes[i];
    }
    
    return await this.bleManager.writeCharacteristic(
      serviceUuid,
      characteristicUuid,
      data
    );
  }

  /**
   * 启用通知
   */
  public async enableNotification(
    serviceUuid: string,
    characteristicUuid: string,
    callback: (data: ArrayBuffer) => void
  ): Promise<boolean> {
    const key = `${serviceUuid}:${characteristicUuid}`;
    
    // 保存回调
    this.notificationCallbacks.set(key, callback);
    
    // 设置全局回调
    this.bleManager.setOnCharacteristicChanged((data: ArrayBuffer) => {
      // 这里简化处理,实际需要根据特征值UUID分发
      callback(data);
    });
    
    return await this.bleManager.enableNotification(
      serviceUuid,
      characteristicUuid,
      true
    );
  }

  /**
   * 禁用通知
   */
  public async disableNotification(
    serviceUuid: string,
    characteristicUuid: string
  ): Promise<boolean> {
    const key = `${serviceUuid}:${characteristicUuid}`;
    this.notificationCallbacks.delete(key);
    
    return await this.bleManager.enableNotification(
      serviceUuid,
      characteristicUuid,
      false
    );
  }

  /**
   * 数据转换工具方法
   */
  private arrayBufferToString(data: ArrayBuffer): string {
    const view = new Uint8Array(data);
    let result = '';
    for (let i = 0; i < view.length; i++) {
      result += String.fromCharCode(view[i]);
    }
    return result;
  }

  private stringToArrayBuffer(str: string): ArrayBuffer {
    const buffer = new ArrayBuffer(str.length);
    const view = new Uint8Array(buffer);
    for (let i = 0; i < str.length; i++) {
      view[i] = str.charCodeAt(i);
    }
    return buffer;
  }

  private arrayBufferToNumber(
    data: ArrayBuffer,
    bitLength: number,
    signed: boolean
  ): number {
    const view = new DataView(data);
    
    switch (bitLength) {
      case 8:
        return signed ? view.getInt8(0) : view.getUint8(0);
      case 16:
        return signed ? view.getInt16(0, true) : view.getUint16(0, true);
      case 32:
        return signed ? view.getInt32(0, true) : view.getUint32(0, true);
      default:
        return view.getUint8(0);
    }
  }

  private numberToArrayBuffer(value: number, bitLength: number): ArrayBuffer {
    const buffer = new ArrayBuffer(bitLength / 8);
    const view = new DataView(buffer);
    
    switch (bitLength) {
      case 8:
        view.setUint8(0, value);
        break;
      case 16:
        view.setUint16(0, value, true);
        break;
      case 32:
        view.setUint32(0, value, true);
        break;
    }
    
    return buffer;
  }
}

3.3 完整的设备控制示例

import { GattServiceManager } from './GattServiceManager';
import { GattDataOperator } from './GattDataOperator';

/**
 * 智能手环控制器
 * 综合示例:心率、步数、电池等数据读取
 */
export class SmartBandController {
  private serviceManager: GattServiceManager = new GattServiceManager();
  private dataOperator: GattDataOperator = new GattDataOperator();
  
  // 标准服务UUID
  private readonly HEART_RATE_SERVICE = '0000180D-0000-1000-8000-00805F9B34FB';
  private readonly HEART_RATE_MEASUREMENT = '00002A37-0000-1000-8000-00805F9B34FB';
  
  private readonly BATTERY_SERVICE = '0000180F-0000-1000-8000-00805F9B34FB';
  private readonly BATTERY_LEVEL = '00002A19-0000-1000-8000-00805F9B34FB';
  
  private readonly DEVICE_INFO_SERVICE = '0000180A-0000-1000-8000-00805F9B34FB';
  private readonly MANUFACTURER_NAME = '00002A29-0000-1000-8000-00805F9B34FB';
  private readonly MODEL_NUMBER = '00002A24-0000-1000-8000-00805F9B34FB';
  private readonly FIRMWARE_REVISION = '00002A26-0000-1000-8000-00805F9B34FB';

  // 回调
  private onHeartRateChanged: ((rate: number) => void) | null = null;
  private onBatteryChanged: ((level: number) => void) | null = null;

  /**
   * 初始化
   */
  public async init(): Promise<boolean> {
    return await this.serviceManager.init();
  }

  /**
   * 获取设备信息
   */
  public async getDeviceInfo(): Promise<{
    manufacturer: string;
    model: string;
    firmware: string;
  }> {
    const manufacturer = await this.dataOperator.readString(
      this.DEVICE_INFO_SERVICE,
      this.MANUFACTURER_NAME
    );
    
    const model = await this.dataOperator.readString(
      this.DEVICE_INFO_SERVICE,
      this.MODEL_NUMBER
    );
    
    const firmware = await this.dataOperator.readString(
      this.DEVICE_INFO_SERVICE,
      this.FIRMWARE_REVISION
    );
    
    return {
      manufacturer: manufacturer || 'Unknown',
      model: model || 'Unknown',
      firmware: firmware || 'Unknown'
    };
  }

  /**
   * 读取电池电量
   */
  public async getBatteryLevel(): Promise<number> {
    const level = await this.dataOperator.readNumber(
      this.BATTERY_SERVICE,
      this.BATTERY_LEVEL,
      8
    );
    return level || 0;
  }

  /**
   * 开始心率监测
   */
  public async startHeartRateMonitor(): Promise<boolean> {
    return await this.dataOperator.enableNotification(
      this.HEART_RATE_SERVICE,
      this.HEART_RATE_MEASUREMENT,
      (data: ArrayBuffer) => {
        const heartRate = this.parseHeartRateData(data);
        if (this.onHeartRateChanged) {
          this.onHeartRateChanged(heartRate);
        }
      }
    );
  }

  /**
   * 停止心率监测
   */
  public async stopHeartRateMonitor(): Promise<boolean> {
    return await this.dataOperator.disableNotification(
      this.HEART_RATE_SERVICE,
      this.HEART_RATE_MEASUREMENT
    );
  }

  /**
   * 解析心率数据
   * 根据蓝牙心率服务规范
   */
  private parseHeartRateData(data: ArrayBuffer): number {
    const view = new DataView(data);
    const flags = view.getUint8(0);
    
    // 检查心率值格式
    const is16Bit = (flags & 0x01) === 1;
    
    if (is16Bit) {
      return view.getUint16(1, true);
    } else {
      return view.getUint8(1);
    }
  }

  /**
   * 设置回调
   */
  public setOnHeartRateChanged(callback: (rate: number) => void): void {
    this.onHeartRateChanged = callback;
  }

  public setOnBatteryChanged(callback: (level: number) => void): void {
    this.onBatteryChanged = callback;
  }
}

/**
 * 智能家居控制器示例
 */
export class SmartHomeController {
  private serviceManager: GattServiceManager = new GattServiceManager();
  private dataOperator: GattDataOperator = new GattDataOperator();
  
  // 自定义服务UUID
  private readonly HOME_SERVICE = '0000FFF0-0000-1000-8000-00805F9B34FB';
  private readonly SWITCH_CHAR = '0000FFF1-0000-1000-8000-00805F9B34FB';
  private readonly BRIGHTNESS_CHAR = '0000FFF2-0000-1000-8000-00805F9B34FB';
  private readonly COLOR_CHAR = '0000FFF3-0000-1000-8000-00805F9B34FB';
  private readonly TEMPERATURE_CHAR = '0000FFF4-0000-1000-8000-00805F9B34FB';

  /**
   * 初始化
   */
  public async init(): Promise<boolean> {
    return await this.serviceManager.init();
  }

  /**
   * 开灯
   */
  public async turnOnLight(): Promise<boolean> {
    return await this.dataOperator.writeBytes(
      this.HOME_SERVICE,
      this.SWITCH_CHAR,
      [0x01]
    );
  }

  /**
   * 关灯
   */
  public async turnOffLight(): Promise<boolean> {
    return await this.dataOperator.writeBytes(
      this.HOME_SERVICE,
      this.SWITCH_CHAR,
      [0x00]
    );
  }

  /**
   * 设置亮度
   * @param brightness 亮度值 0-100
   */
  public async setBrightness(brightness: number): Promise<boolean> {
    const value = Math.max(0, Math.min(100, brightness));
    return await this.dataOperator.writeBytes(
      this.HOME_SERVICE,
      this.BRIGHTNESS_CHAR,
      [value]
    );
  }

  /**
   * 设置颜色(RGB)
   */
  public async setColor(r: number, g: number, b: number): Promise<boolean> {
    return await this.dataOperator.writeBytes(
      this.HOME_SERVICE,
      this.COLOR_CHAR,
      [
        Math.max(0, Math.min(255, r)),
        Math.max(0, Math.min(255, g)),
        Math.max(0, Math.min(255, b))
      ]
    );
  }

  /**
   * 读取温度
   */
  public async getTemperature(): Promise<number> {
    // 假设温度是16位有符号整数,单位0.01℃
    const rawValue = await this.dataOperator.readNumber(
      this.HOME_SERVICE,
      this.TEMPERATURE_CHAR,
      16,
      true
    );
    
    if (rawValue === null) return 0;
    return rawValue / 100; // 转换为摄氏度
  }
}

四、踩坑与注意事项

4.1 UUID格式问题

坑点:UUID格式不统一,需要规范化处理。

/**
 * UUID规范化
 */
function normalizeUuid(uuid: string): string {
  // 移除所有连字符
  let normalized = uuid.replace(/-/g, '').toUpperCase();
  
  // 补全128位UUID
  if (normalized.length === 4) {
    // 16位UUID -> 128位
    normalized = `0000${normalized}00001000800000805F9B34FB`;
  } else if (normalized.length === 8) {
    // 32位UUID -> 128位
    normalized = `${normalized}00001000800000805F9B34FB`;
  }
  
  // 添加连字符
  return [
    normalized.substring(0, 8),
    normalized.substring(8, 12),
    normalized.substring(12, 16),
    normalized.substring(16, 20),
    normalized.substring(20, 32)
  ].join('-');
}

// 使用示例
const shortUuid = '180D';  // 心率服务
const fullUuid = normalizeUuid(shortUuid);
console.info(fullUuid);  // 0000180D-0000-1000-8000-00805F9B34FB

4.2 操作队列管理

坑点:GATT操作不能并发执行,需要排队。

/**
 * GATT操作队列
 * 确保操作顺序执行
 */
export class GattOperationQueue {
  private queue: Array<() => Promise<any>> = [];
  private isProcessing: boolean = false;

  /**
   * 添加操作到队列
   */
  public async enqueue<T>(operation: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          const result = await operation();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });
      
      this.processQueue();
    });
  }

  /**
   * 处理队列
   */
  private async processQueue(): Promise<void> {
    if (this.isProcessing || this.queue.length === 0) {
      return;
    }

    this.isProcessing = true;
    
    while (this.queue.length > 0) {
      const operation = this.queue.shift();
      if (operation) {
        await operation();
      }
    }
    
    this.isProcessing = false;
  }

  /**
   * 清空队列
   */
  public clear(): void {
    this.queue = [];
  }
}

// 使用示例
const operationQueue = new GattOperationQueue();

// 所有GATT操作都通过队列执行
async function safeRead(serviceUuid: string, charUuid: string): Promise<ArrayBuffer | null> {
  return await operationQueue.enqueue(() => {
    return bleManager.readCharacteristic(serviceUuid, charUuid);
  });
}

4.3 数据解析错误处理

坑点:设备返回的数据格式可能不符合预期。

/**
 * 安全的数据解析
 */
function safeParseHeartRate(data: ArrayBuffer): number | null {
  try {
    if (data.byteLength < 2) {
      console.error('[心率] 数据长度不足');
      return null;
    }
    
    const view = new DataView(data);
    const flags = view.getUint8(0);
    const is16Bit = (flags & 0x01) === 1;
    
    // 检查数据长度是否足够
    if (is16Bit && data.byteLength < 3) {
      console.error('[心率] 16位心率数据长度不足');
      return null;
    }
    
    const heartRate = is16Bit ? view.getUint16(1, true) : view.getUint8(1);
    
    // 合理性检查
    if (heartRate < 30 || heartRate > 220) {
      console.warn(`[心率] 数值异常: ${heartRate}`);
      return null;
    }
    
    return heartRate;
  } catch (error) {
    console.error('[心率] 解析失败:', error);
    return null;
  }
}

4.4 连接状态检查

坑点:操作前需要确认连接状态。

/**
 * 安全的GATT操作包装
 */
async function safeGattOperation<T>(
  operation: () => Promise<T>,
  deviceCheck: () => boolean
): Promise<T | null> {
  // 检查连接状态
  if (!deviceCheck()) {
    console.error('[GATT] 设备未连接');
    return null;
  }
  
  try {
    return await operation();
  } catch (error) {
    const err = error as BusinessError;
    
    // 处理特定错误
    if (err.code === 133) {
      console.error('[GATT] GATT操作失败,可能连接已断开');
      // 触发重连逻辑
    }
    
    return null;
  }
}

4.5 描述符操作

坑点:有时需要直接操作描述符。

/**
 * 读取描述符
 */
async function readDescriptor(
  serviceUuid: string,
  characteristicUuid: string,
  descriptorUuid: string
): Promise<ArrayBuffer | null> {
  if (!gattClient) return null;
  
  try {
    const value = await gattClient.readDescriptorValue({
      serviceUuid: serviceUuid,
      characteristicUuid: characteristicUuid,
      descriptorUuid: descriptorUuid
    });
    
    return value;
  } catch (error) {
    console.error('[GATT] 读取描述符失败:', error);
    return null;
  }
}

/**
 * 写入描述符
 * 用于手动控制CCCD
 */
async function writeCccd(
  serviceUuid: string,
  characteristicUuid: string,
  enable: boolean
): Promise<boolean> {
  const CCCD_UUID = '00002902-0000-1000-8000-00805F9B34FB';
  
  const data = new ArrayBuffer(2);
  new DataView(data).setUint16(0, enable ? 0x0001 : 0x0000, true);
  
  if (!gattClient) return false;
  
  try {
    await gattClient.writeDescriptorValue({
      serviceUuid: serviceUuid,
      characteristicUuid: characteristicUuid,
      descriptorUuid: CCCD_UUID,
      descriptorValue: data
    });
    
    return true;
  } catch (error) {
    console.error('[GATT] 写入CCCD失败:', error);
    return false;
  }
}

五、HarmonyOS 6适配

5.1 API变更

API HarmonyOS 5 HarmonyOS 6 说明
discoverServices() 返回服务列表 返回增强的服务对象 包含更多属性
readCharacteristicValue() 基础功能 支持超时设置 新增timeout参数
新增 - getCharacteristicProperties() 独立获取属性

适配代码

/**
 * HarmonyOS 6 GATT适配
 */
async function readCharacteristicAdaptive(
  serviceUuid: string,
  characteristicUuid: string,
  timeout?: number
): Promise<ArrayBuffer | null> {
  const apiVersion = getApiVersion();

  try {
    if (apiVersion >= 6 && timeout) {
      // HarmonyOS 6 支持超时
      return await gattClient.readCharacteristicValue({
        serviceUuid: serviceUuid,
        characteristicUuid: characteristicUuid
      }, { timeout: timeout });
    } else {
      // HarmonyOS 5
      return await gattClient.readCharacteristicValue({
        serviceUuid: serviceUuid,
        characteristicUuid: characteristicUuid
      });
    }
  } catch (error) {
    console.error('[GATT] 读取失败:', error);
    return null;
  }
}

5.2 新增功能

HarmonyOS 6增强了GATT能力:

// 1. 批量读取
const multipleValues = await gattClient.readMultipleCharacteristicValues([
  { serviceUuid: service1, characteristicUuid: char1 },
  { serviceUuid: service2, characteristicUuid: char2 }
]);

// 2. 可靠写入(Reliable Write)
const reliableWrite = gattClient.beginReliableWrite();
await reliableWrite.writeCharacteristicValue({ ... });
await reliableWrite.writeCharacteristicValue({ ... });
await reliableWrite.execute();  // 原子提交

// 3. 长读/写支持
const longValue = await gattClient.readLongCharacteristicValue({
  serviceUuid: serviceUuid,
  characteristicUuid: characteristicUuid,
  offset: 0,
  maxLength: 512
});

// 4. 服务变更通知
gattClient.on('serviceChange', () => {
  console.info('[GATT] 服务已变更,重新发现');
  discoverServices();
});

5.3 性能优化

HarmonyOS 6提供了更多性能调优选项:

// 1. 连接参数更新
await gattClient.requestConnectionPriority(
  bluetooth.ConnectionPriority.CONNECTION_PRIORITY_HIGH
);

// 2. 数据长度扩展
await gattClient.requestDataLength(251);  // 请求251字节数据长度

// 3. PHY设置
await gattClient.requestPreferredPhy({
  txPhy: bluetooth.BlePhy.PHY_2M,
  rxPhy: bluetooth.BlePhy.PHY_2M,
  phyOptions: bluetooth.PhyOption.PHY_OPTION_NO_PREFERRED
});

六、总结

GATT是BLE数据交换的核心,掌握服务与特征值操作是BLE开发的关键技能。本文全面讲解了GATT的核心要点:

核心要点回顾

  1. GATT架构:理解服务、特征值、描述符的层次关系
  2. UUID管理:区分标准UUID和自定义UUID,做好格式规范化
  3. 属性检查:操作前检查特征值是否支持对应属性
  4. 数据转换:正确处理ArrayBuffer与各种数据类型的转换
  5. 操作队列:避免并发操作,确保顺序执行

最佳实践建议

  • 封装GATT服务管理类,缓存服务和特征值信息
  • 实现操作队列,避免并发问题
  • 做好数据解析的错误处理和合理性检查
  • 使用标准UUID提高兼容性

下一步学习

  • 蓝牙经典蓝牙RFCOMM通信(下一篇文章)
  • BLE广播数据解析
  • BLE安全机制
  • 多设备同时连接管理

GATT看似复杂,其实就是一套"发现-读写-通知"的机制。希望本文能帮助你掌握GATT操作的核心技能,在BLE开发中游刃有余!

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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