实现蓝牙设备测量完整流程
本文剖析前端蓝牙设备测量的代码实现,介绍其模块化架构。包含蓝牙连接模块(初始化适配器、检查权限)、设备连接模块(发现、连接设备)、设备测量模块(发送指令、处理结果)、设备上报模块(处理并上报数据)。各模块分工明确,通过错误处理机制保障稳定性,还封装为 BluetoothPlugin 工具供调用,可满足不同蓝牙设备测量需求。
整体架构
采用模块化的设计思想,将蓝牙连接、设备连接、设备测量和设备上报等功能拆分成独立的模块,每个模块负责特定的任务,提高代码的可维护性和可扩展性。以下是主要的模块及其功能:
1. 蓝牙连接模块(bluetoothConnection.js)
此模块主要负责蓝牙适配器的初始化和权限检查。代码中通过 getRuntime 函数判断当前运行环境是 uni 还是 wx,并获取相应的平台 API。checkPermissions 函数用于检查蓝牙权限,如果未授权则请求授权。initBluetoothAdapter 函数用于初始化蓝牙适配器,并设置监听事件,当蓝牙适配器状态变化时会触发相应的处理逻辑。
const getRuntime = () => {
if (typeof uni !== 'undefined') return uni
if (typeof wx !== 'undefined') return wx
throw new Error('Unsupported runtime')
}
const platformApi = getRuntime()
const checkPermissions = () =>
new Promise((resolve, reject) => {
platformApi.getSetting({
success: ({ authSetting }) => {
if (authSetting["scope.bluetooth"] === true) return resolve();
platformApi.authorize({
scope: "scope.bluetooth",
success: resolve,
fail: () => {
platformApi.showModal({
title: "权限要求",
content: "需要蓝牙权限以连接设备",
success: ({ confirm }) =>
confirm ? platformApi.openSetting() : reject(),
});
},
});
},
fail: reject,
});
});
const initBluetoothAdapter = () => {
return new Promise((resolve, reject) => {
platformApi.openBluetoothAdapter({
success: () => {
setupBluetoothAdapterListeners();
resolve();
},
fail: (err) => {
if (err.errMsg === "openBluetoothAdapter:fail already opened") {
setupBluetoothAdapterListeners();
resolve();
} else {
reject(err);
}
},
});
});
};
2. 设备连接模块(deviceConnection.js)
该模块负责设备的发现、连接和断开连接等操作。startBluetoothDiscovery 函数用于启动设备发现,会在一定时间后自动停止扫描,并根据连接模式决定是否自动连接设备。handleDeviceFound 函数处理发现的设备,过滤掉不符合条件的设备,并更新设备列表。connectDevice 函数是设备连接的核心逻辑,包括创建 BLE 连接、获取服务和特征值、启用通知等步骤。
const startBluetoothDiscovery = () => {
return new Promise(async (resolve, reject) => {
const { available } = await getBluetoothAdapterState();
if (!available) {
errorHandler(
ErrorTypes.BLUETOOTH_CONNECTION_FAILED,
"蓝牙适配器不可用",
new Error("蓝牙适配器不可用")
);
reject(new Error("蓝牙适配器不可用"));
return;
}
if (scanTimer) {
clearTimeout(scanTimer);
}
scanTimer = setTimeout(async () => {
await stopBluetoothDiscovery();
if (config.connectionMode === "auto") {
autoConnectDevice(false);
}
resolve();
}, scanTime * 1000);
await new Promise((resolve, reject) => {
platformApi.startBluetoothDevicesDiscovery({
services: serviceId ? [serviceId] : void 0,
allowDuplicatesKey: false,
success: () => {
platformApi.onBluetoothDeviceFound(handleDeviceFound);
resolve();
},
fail: (err) => {
console.log("扫描错误", err);
if (err.errMsg.includes("already started")) {
resolve();
} else {
errorHandler(
ErrorTypes.DEVICE_CONNECTION_FAILED,
`扫描设备失败: ${err.errMsg}`,
err
);
reject(err);
}
},
});
});
});
};
const connectDevice = (device) => {
return new Promise(async (resolve, reject) => {
try {
await createBLEConnection(device.deviceId);
const services = await getBLEDeviceServices(device.deviceId);
const primaryService = services.find((s) => s.uuid === serviceId);
if (!primaryService) throw new Error("未找到指定服务");
const characteristics = await getBLEDeviceCharacteristics(
device.deviceId,
primaryService.uuid
);
const { writeChar, notifyChar } = characteristicMatch(characteristics);
if (!writeChar || !notifyChar) throw new Error("缺少必要特征值");
selectedDevice = device;
writeChar = writeChar;
notifyChar = notifyChar;
await enableNotifications(
device.deviceId,
primaryService.uuid,
notifyChar.uuid
);
setupDeviceListeners(device.deviceId);
resolve();
} catch (error) {
if (error.message === "未找到指定服务") {
errorHandler(ErrorTypes.SERVICE_NOT_FOUND, error.message, error);
} else if (error.message === "缺少必要特征值") {
errorHandler(
ErrorTypes.CHARACTERISTIC_NOT_FOUND,
error.message,
error
);
} else {
errorHandler(
ErrorTypes.DEVICE_CONNECTION_FAILED,
error.message,
error
);
}
reject(error);
}
});
};
3. 设备测量模块(deviceMeasurement.js)
该模块负责向蓝牙设备发送测量指令并处理测量结果。startMeasurement 函数会检查设备是否连接和特征值是否获取,如果条件满足则生成通信数据并发送给设备,同时监听设备返回的测量结果。handleDataReceived 函数处理接收到的测量数据。
const startMeasurement = () => {
if (!selectedDevice || !writeChar || !notifyChar) {
errorHandler(ErrorTypes.DEVICE_CONNECTION_FAILED, '设备未连接或特征值未获取', new Error('设备未连接或特征值未获取'));
return;
}
const buffer = communicationData();
platformApi.writeBLECharacteristicValue({
deviceId: selectedDevice.deviceId,
serviceId,
characteristicId: writeChar.uuid,
value: buffer,
success: res => {
console.log('发送指令成功', res);
platformApi.onBLECharacteristicValueChange(handleDataReceived);
},
fail: res => {
errorHandler(res);
}
});
};
const handleDataReceived = res => {
console.log('测量结果', res);
measureData = res
};
4. 设备上报模块(deviceReport.js)
该模块负责将测量数据上报到指定的位置。setMeasureData 函数用于设置测量数据,reportData 函数会检查是否有测量数据,如果有则对数据进行处理并调用上报方法。
const setMeasureData = (data) => {
measureData = data;
};
const reportData = () => {
if (!measureData) {
errorHandler(ErrorTypes.NO_MEASUREMENT_DATA, '没有测量数据可供上报', new Error('没有测量数据可供上报'));
return;
}
const processedData = reportDataHandler(measureData);
try {
reportMethod(processedData);
} catch (error) {
errorHandler(ErrorTypes.NO_MEASUREMENT_DATA, error.message, error);
}
};
错误处理
项目中定义了各种错误类型(ErrorTypes),并在各个模块中进行了相应的错误处理。当出现错误时,会调用 errorHandler 函数,将错误类型、错误信息和错误对象传递给该函数进行统一处理,提高了代码的健壮性。
const ErrorTypes = {
BLUETOOTH_CONNECTION_FAILED: 'BLUETOOTH_CONNECTION_FAILED',
DEVICE_CONNECTION_FAILED: 'DEVICE_CONNECTION_FAILED',
DEVICE_NOT_FOUND: 'DEVICE_NOT_FOUND',
SERVICE_NOT_FOUND: 'SERVICE_NOT_FOUND',
CHARACTERISTIC_NOT_FOUND: 'CHARACTERISTIC_NOT_FOUND',
NOTIFICATION_ENABLE_FAILED: 'NOTIFICATION_ENABLE_FAILED',
WRITE_DATA_FAILED: 'WRITE_DATA_FAILED',
NO_MEASUREMENT_DATA: 'NO_MEASUREMENT_DATA'
};
// 在各个模块中使用 errorHandler 处理错误
errorHandler(ErrorTypes.DEVICE_CONNECTION_FAILED, error.message, error);
初始化
通过 BluetoothPlugin 函数将各个模块的功能进行整合,用户可以传入自定义的配置项,函数会合并默认配置和用户配置,并返回可调用的方法。用户可以通过这些方法实现蓝牙连接、设备发现、测量和上报等功能。
function BluetoothPlugin(config) {
const defaultConfig = {
connectionMode: 'auto',
serviceId: '',
onDeviceFound: (devices, deviceList) => {
console.log('设备发现:', devices);
console.log('设备列表:', deviceList);
},
characteristicMatch: (characteristics) => {
const writeChar = characteristics.find(c => c.properties.write);
const notifyChar = characteristics.find(c => c.properties.notify);
return { writeChar, notifyChar };
},
communicationData: () => {
let buffer = new ArrayBuffer(6);
let dataView = new DataView(buffer);
dataView.setUint8(0, 0x02);
dataView.setUint8(1, 0x40);
dataView.setUint8(2, 0xdc);
dataView.setUint8(3, 0x01);
dataView.setUint8(4, 0xa1);
dataView.setUint8(5, 0x3c);
return buffer;
},
reportDataHandler: (data) => {
return data;
},
reportMethod: (data) => {
console.log('Reporting data:', data);
},
errorHandler: (err) => {
console.log('Error:', err);
}
};
config = Object.assign({}, defaultConfig, config);
const { initBluetooth, disconnectBluetoothAdapter } = bluetoothConnection(config);
const { connectDevice, startBluetoothDiscovery, stopBluetoothDiscovery, getDeviceInfo, disconnectDevice } = deviceConnection(config);
const { startMeasurement, setDeviceInfo, getMeasureData } = deviceMeasurement(config);
const { reportData, setMeasureData } = deviceReport(config);
return {
connectBluetooth: initBluetooth,
disconnectBluetoothAdapter,
startBluetoothDiscovery,
stopBluetoothDiscovery,
connectDevice,
disconnectDevice,
startMeasurement: () => {
setDeviceInfo(getDeviceInfo())
startMeasurement()
},
reportData: () => {
setMeasureData(getMeasureData())
reportData()
},
};
}
使用
import { ref, computed, onMounted, onUnmounted } from "vue";
import { BluetoothPlugin, ErrorTypes } from "bluetooth-pressure-measure";
const config = {
connectionMode: 'auto', // 'auto' or 'manual'
serviceId: '',
onDeviceFound: (devices, deviceList) => {
console.log('设备发现:', devices);
console.log('设备列表:', deviceList);
},
characteristicMatch: (characteristics) => {
const writeChar = characteristics.find(c => c.properties.write);
const notifyChar = characteristics.find(c => c.properties.notify);
return { writeChar, notifyChar };
},
communicationData: () => {
let buffer = new ArrayBuffer(6);
let dataView = new DataView(buffer);
dataView.setUint8(0, 0x02);
dataView.setUint8(1, 0x40);
dataView.setUint8(2, 0xdc);
dataView.setUint8(3, 0x01);
dataView.setUint8(4, 0xa1);
dataView.setUint8(5, 0x3c);
return buffer;
},
reportDataHandler: (data) => {
return data;
},
reportMethod: (data) => {
// Implement your reporting logic here
console.log('Reporting data:', data);
},
errorHandler: (errorType, message, error) => {
switch (errorType) {
case ErrorTypes.BLUETOOTH_CONNECTION_FAILED:
console.log('蓝牙连接失败:', message, error);
break;
case ErrorTypes.DEVICE_CONNECTION_FAILED:
console.log('设备连接失败:', message, error);
break;
case ErrorTypes.DEVICE_NOT_FOUND:
console.log('未找到设备:', message, error);
break;
// 其他异常类型处理
default:
console.log('未知异常:', message, error);
break;
}
}
};
完整代码
总结
通过分析这个前端蓝牙设备测量的代码示例,我们可以看到实现蓝牙设备交互和测量功能需要经过多个步骤,包括蓝牙适配器初始化、设备发现、连接、数据发送和接收以及数据上报等。模块化的设计思想使得代码结构清晰,易于维护和扩展。同时,合理的错误处理机制提高了代码的健壮性,确保在出现异常情况时能够及时处理。在实际开发中,我们可以根据具体需求对代码进行进一步的优化和扩展,以满足不同蓝牙设备的测量需求。