187 lines
4.3 KiB
JavaScript
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
|
|
}
|
|
}
|