SEC_Warehouse/hooks/useBluetoothDiscovery.js

187 lines
4.3 KiB
JavaScript

import {
BLUETOOTH_ERROR_MESSAGES,
DEFAULT_BLUETOOTH_ERROR_MESSAGE,
TARGET_SERVICE_UUID,
TARGET_SHORT_UUID
} from '@/constants/bluetooth'
import { showToast } from '@/utils/toast'
/**
* 蓝牙设备搜索逻辑封装。
* 通过回调把状态同步给页面,减少页面文件复杂度。
*/
export function createBluetoothDiscovery(options = {}) {
const {
onSearchingChange = () => {},
onDeviceMapChange = () => {},
onDeviceListChange = () => {}
} = options
let isSearching = false
let deviceMap = {}
let deviceFoundHandler = null
function setSearching(value) {
isSearching = Boolean(value)
onSearchingChange(isSearching)
}
function setDeviceMap(nextMap) {
deviceMap = nextMap
onDeviceMapChange({ ...deviceMap })
emitDeviceList()
}
function emitDeviceList() {
const list = Object.values(deviceMap).sort((a, b) => {
if (a.hasTargetService !== b.hasTargetService) {
return a.hasTargetService ? -1 : 1
}
const aSignal = typeof a.RSSI === 'number' ? a.RSSI : -999
const bSignal = typeof b.RSSI === 'number' ? b.RSSI : -999
return bSignal - aSignal
})
onDeviceListChange(list)
}
function normalizeUuid(uuid) {
return String(uuid || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase()
}
function hasTargetService(device) {
if (!Array.isArray(device.advertisServiceUUIDs)) {
return false
}
const targetUuid = normalizeUuid(TARGET_SERVICE_UUID)
return device.advertisServiceUUIDs.some((uuid) => {
const value = normalizeUuid(uuid)
return value === targetUuid || value.endsWith(TARGET_SHORT_UUID)
})
}
function openBluetoothAdapter() {
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: resolve,
fail: reject
})
})
}
function startDiscoveryWithServices(services = []) {
return new Promise((resolve, reject) => {
const options = {
allowDuplicatesKey: true,
interval: 0,
success: resolve,
fail: reject
}
if (services.length) {
options.services = services
}
uni.startBluetoothDevicesDiscovery(options)
})
}
function upsertDevice(rawDevice) {
if (!rawDevice || !rawDevice.deviceId) {
return
}
const existing = deviceMap[rawDevice.deviceId] || {}
const merged = {
...existing,
...rawDevice,
showName: rawDevice.name || rawDevice.localName || existing.showName || '未命名设备',
hasTargetService: hasTargetService(rawDevice) || existing.hasTargetService
}
setDeviceMap({
...deviceMap,
[rawDevice.deviceId]: merged
})
}
function registerDeviceFoundHandler() {
if (deviceFoundHandler) {
return
}
deviceFoundHandler = (res) => {
const devices = Array.isArray(res.devices) ? res.devices : [res]
devices.forEach((device) => {
upsertDevice(device)
})
}
uni.onBluetoothDeviceFound(deviceFoundHandler)
}
function stopSearch(showStopToast = true) {
return new Promise((resolve) => {
uni.stopBluetoothDevicesDiscovery({
complete: () => {
setSearching(false)
if (showStopToast) {
showToast('已停止搜索')
}
resolve()
}
})
})
}
function cleanupDiscovery() {
if (isSearching) {
uni.stopBluetoothDevicesDiscovery({
complete: () => {}
})
}
setSearching(false)
if (deviceFoundHandler && uni.offBluetoothDeviceFound) {
uni.offBluetoothDeviceFound(deviceFoundHandler)
}
deviceFoundHandler = null
}
function getBluetoothErrorText(error) {
const errCode = error && error.errCode
return BLUETOOTH_ERROR_MESSAGES[errCode] || DEFAULT_BLUETOOTH_ERROR_MESSAGE
}
async function startSearch() {
setDeviceMap({})
try {
await openBluetoothAdapter()
registerDeviceFoundHandler()
try {
await startDiscoveryWithServices([TARGET_SERVICE_UUID])
} catch (error) {
await startDiscoveryWithServices([])
}
setSearching(true)
} catch (error) {
setSearching(false)
showToast(getBluetoothErrorText(error))
throw error
}
}
return {
startSearch,
stopSearch,
cleanupDiscovery
}
}