BLE蓝牙基础通信:扫描、连接、收发数据,IoT简易Demo
BLE蓝牙基础通信:扫描、连接、收发数据,IoT简易Demo
蓝牙低功耗(BLE)是鸿蒙IoT开发里最常用的近场通信方式:智能灯泡、手环、传感器、门锁,几乎所有硬件设备都支持BLE连接。很多开发者第一次接蓝牙时,总觉得流程绕——扫描、连接、发现服务、读特征值、写数据、开通知,一套下来步骤多,稍不注意就卡住。本文基于最新鸿蒙API,把BLE通信的完整流程拆成几个环节讲清楚,最后给一个控制灯泡开关的简易Demo,代码可以直接复制到项目里跑。
01 权限前置与蓝牙开关
BLE属于敏感硬件权限,必须先在module.json5声明,再动态申请。很多人代码写得没问题,结果一直扫描不到设备,十有八九是权限没配或者蓝牙开关没打开。需要的权限有两个:
-
ohos.permission.ACCESS_BLUETOOTH:基础蓝牙权限,用于扫描和连接
-
ohos.permission.APPROXIMATELY_LOCATION:模糊定位权限,Android 12以上和鸿蒙都要求,扫描BLE设备必须要有定位权限
module.json5里这样配置:
"requestPermissions": [
{
"name": "ohos.permission.ACCESS_BLUETOOTH",
"reason": "$string:ble_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:location_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
代码里先判断蓝牙开关是否打开,没开的话引导用户去系统设置打开:
import { bluetooth } from '@kit.ConnectivityKit';
// 获取蓝牙代理对象
const bleManager: bluetooth.BLECentralManager = bluetooth.getBLECentralManager();
// 检查蓝牙是否开启
function isBluetoothEnabled(): boolean {
const state = bleManager.getState();
return state === bluetooth.BluetoothState.TURNED_ON;
}
// 如果没开,跳转到系统蓝牙设置页
function openBluetoothSettings() {
// 这里可以通过 want 跳转系统蓝牙设置界面
console.info('请先打开蓝牙开关');
}
权限申请和蓝牙开关检查要在扫描之前做,不然调用扫描接口直接报错。
02 扫描周边BLE设备
扫描是BLE通信的第一步,打开手机蓝牙后,周围所有正在广播的BLE设备都会被扫到。扫描结果是异步返回的,需要注册回调。
import { bluetooth } from '@kit.ConnectivityKit';
interface ScannedDevice {
deviceId: string;
name: string;
rssi: number; // 信号强度
}
const deviceList: ScannedDevice[] = [];
// 注册扫描结果回调
bleManager.on('BLEDeviceFind', (data: bluetooth.ScanResult[]) => {
data.forEach(item => {
const device = item.device;
const name = device.name || '未知设备';
// 去重,同一个设备只加一次
if (!deviceList.find(d => d.deviceId === device.address)) {
deviceList.push({
deviceId: device.address,
name: name,
rssi: item.rssi
});
console.info('发现设备:', name, device.address, '信号:', item.rssi);
}
});
});
// 开始扫描
function startScan() {
const filter: bluetooth.ScanFilter = {
// 可以按服务UUID过滤,只扫特定设备
// serviceUuid: '0000fff0-0000-1000-8000-00805f9b34fb'
};
bleManager.startBLEScan([filter]);
}
// 停止扫描,扫描很耗电,扫到目标设备后立刻停
function stopScan() {
bleManager.stopBLEScan();
}
几个关键点:
-
扫描很耗电,不要一直扫。一般扫10秒左右,找到目标设备就立刻stopScan
-
信号强度rssi,数值越大信号越好(-40左右很好,-80以下就比较远了)
-
过滤条件,如果知道目标设备的服务UUID,加上filter可以快速过滤掉无关设备,减少回调数量
-
去重,同一个设备会反复上报,必须按deviceId去重,不然列表会刷个不停
03 连接目标设备
扫到目标设备后,就可以发起连接。连接是异步的,需要监听连接状态变化。
let gattClient: bluetooth.GATTClient | null = null;
// 监听连接状态变化
bleManager.on('BLEConnectionStateChange', (state: bluetooth.ConnectionState) => {
console.info('连接状态变化:', state.state);
if (state.state === bluetooth.ProfileConnectionState.CONNECTED) {
console.info('设备已连接,deviceId:', state.deviceId);
} else if (state.state === bluetooth.ProfileConnectionState.DISCONNECTED) {
console.info('设备已断开');
gattClient = null;
}
});
// 发起连接
async function connectDevice(deviceId: string) {
// 创建GATT客户端
gattClient = await bleManager.createGATTClient(deviceId);
// 连接,autoConnect设为false表示立即连接
await gattClient.connect({
autoConnect: false,
transport: bluetooth.Transport.TRANSPORT_LE
});
}
连接成功之后,下一步就是发现服务。BLE的通信结构是:一个设备包含多个Service(服务),每个Service包含多个Characteristic(特征值),所有数据收发都是针对特征值的。
04 发现服务与特征值
连接成功后,必须先discoverServices,系统才会把设备的服务列表返回给你。不调用这个接口,后面所有读写操作都会失败。
interface BLEService {
uuid: string;
characteristics: BLEChar[];
}
interface BLEChar {
uuid: string;
properties: string[]; // read / write / notify
}
const services: BLEService[] = [];
async function discoverServices() {
if (!gattClient) return;
// 发现所有服务
await gattClient.discoverServices();
const gattServices = gattClient.getServices();
gattServices.forEach(service => {
const svc: BLEService = {
uuid: service.uuid,
characteristics: []
};
// 遍历每个服务下的特征值
const chars = service.getCharacteristics();
chars.forEach(char => {
svc.characteristics.push({
uuid: char.uuid,
properties: [
char.properties.read ? 'read' : '',
char.properties.write ? 'write' : '',
char.properties.notify ? 'notify' : ''
].filter(Boolean)
});
});
services.push(svc);
console.info('服务:', service.uuid);
});
}
拿到服务列表后,就可以根据业务需要找到对应的特征值。比如智能灯泡的控制特征值UUID是已知的,直接定位到那个UUID就行。
05 收发数据:读、写、通知
BLE数据收发有三种方式,对应特征值的三种属性:
-
read(读):主动读取一次特征值的当前值,比如读取传感器的当前温度
-
write(写):向特征值写入数据,比如发送控制命令让灯泡开关
-
notify(通知):设备主动向手机推送数据,比如心率手环实时上报心率
async function readCharacteristic(serviceUuid: string, charUuid: string) {
if (!gattClient) return;
const service = gattClient.getService(serviceUuid);
const char = service.getCharacteristic(charUuid);
// 读取特征值
const value = await gattClient.readCharacteristic(char);
// value是ArrayBuffer,转成Uint8Array处理
const data = new Uint8Array(value);
console.info('读到数据:', data);
}
async function writeCharacteristic(serviceUuid: string, charUuid: string, data: Uint8Array) {
if (!gattClient) return;
const service = gattClient.getService(serviceUuid);
const char = service.getCharacteristic(charUuid);
// 写入数据,writeType设为WRITE_TYPE_DEFAULT表示需要设备应答
await gattClient.writeCharacteristic(char, data.buffer, {
writeType: bluetooth.GATTWriteType.WRITE_TYPE_DEFAULT
});
console.info('写入成功');
}
// 监听通知数据
gattClient.on('BLECharacteristicChange', (char: bluetooth.GATTCharacteristic) => {
const data = new Uint8Array(char.value);
console.info('收到通知数据:', data);
});
async function enableNotify(serviceUuid: string, charUuid: string) {
if (!gattClient) return;
const service = gattClient.getService(serviceUuid);
const char = service.getCharacteristic(charUuid);
// 开启通知
await gattClient.setCharacteristicNotify(char, true);
// 还需要写描述符,很多设备要求这一步
const desc = char.getDescriptor('00002902-0000-1000-8000-00805f9b34fb');
if (desc) {
await gattClient.writeDescriptor(desc, new Uint8Array([1, 0]).buffer);
}
}
数据格式一般是十六进制字节数组,具体怎么解析要看设备的通信协议。比如控制灯泡的命令可能是0x01表示开,0x00表示关,这个要跟硬件工程师确认。
06 IoT简易Demo:控制智能灯泡
把上面的流程串起来,就是一个完整的智能灯泡控制Demo。核心逻辑:扫描设备 → 连接 → 发现服务 → 找到控制特征值 → 写入开关命令。
import { bluetooth } from '@kit.ConnectivityKit';
const bleManager = bluetooth.getBLECentralManager();
let gattClient: bluetooth.GATTClient | null = null;
// 灯泡服务和特征值UUID(实际项目中从设备协议文档获取)
const LIGHT_SERVICE_UUID = '0000fff0-0000-1000-8000-00805f9b34fb';
const LIGHT_WRITE_CHAR_UUID = '0000fff1-0000-1000-8000-00805f9b34fb';
// 扫描并连接目标设备
async function connectLightDevice(targetName: string) {
bleManager.on('BLEDeviceFind', async (results: bluetooth.ScanResult[]) => {
for (const item of results) {
if (item.device.name === targetName) {
bleManager.stopBLEScan();
gattClient = await bleManager.createGATTClient(item.device.address);
await gattClient.connect({ autoConnect: false });
await gattClient.discoverServices();
console.info('灯泡已连接,可以控制了');
break;
}
}
});
bleManager.startBLEScan([]);
}
// 开灯
async function turnOnLight() {
if (!gattClient) return;
const service = gattClient.getService(LIGHT_SERVICE_UUID);
const char = service.getCharacteristic(LIGHT_WRITE_CHAR_UUID);
// 发送开灯命令:0x01
const cmd = new Uint8Array([0x01]);
await gattClient.writeCharacteristic(char, cmd.buffer);
console.info('已开灯');
}
// 关灯
async function turnOffLight() {
if (!gattClient) return;
const service = gattClient.getService(LIGHT_SERVICE_UUID);
const char = service.getCharacteristic(LIGHT_WRITE_CHAR_UUID);
// 发送关灯命令:0x00
const cmd = new Uint8Array([0x00]);
await gattClient.writeCharacteristic(char, cmd.buffer);
console.info('已关灯');
}
// 断开连接,页面退出时调用
async function disconnect() {
if (gattClient) {
await gattClient.disconnect();
gattClient.close();
gattClient = null;
}
}
UI层就两个按钮,开灯和关灯,点击分别调用turnOnLight和turnOffLight就行。实际项目中,连接状态、设备列表可以做成列表页,用户选择设备后进入控制页。
小结
BLE通信的流程其实不复杂,核心就是六步:权限检查 → 扫描设备 → 建立连接 → 发现服务 → 定位特征值 → 读写数据。容易卡的地方主要是权限配置不全、扫描忘记停止、连接成功后没discoverServices、开通知没写描述符。把这些步骤封装成一个统一的BLE工具类,项目里直接调用,不用每次都从头写一遍。实际接硬件的时候,关键是拿到设备的通信协议文档,确认服务UUID、特征值UUID和数据格式,剩下的就是照着流程走。
更多推荐




所有评论(0)