import React, { useRef, useState } from 'react' import Taro from '@tarojs/taro' import { Command } from '@/common/bluetooth/command' import { uint8ArrayToFloat32, uint8ArrayToHex, waitFor } from '@/common/bluetooth/utils' interface params { init: () => void state: Object startScan: () => void measureAndGetLab: () => any getAdapterState: () => void connect: (any) => void disconnect: () => void } const Context = React.createContext(null) export interface BluetoothStateType { listeners: any discovering: boolean available: boolean connected: any connecting: any serviceRule: any serviceId: any characteristicRule: any characteristicId: any /** 正在执行的命令 */ command: any responseResolve: any responseReject: any responseTimer: any /** 是否显示蓝牙调试信息 */ debug: any //搜索到的设备 devices: any //取色仪主动返回的数据 deviceLab: any } let stateObj: BluetoothStateType = { /** 事件监听器 */ listeners: new Set(), /** 正在扫描设备 */ discovering: false, /** 蓝牙是否可用 */ available: true, /** 当前连接的设备 */ connected: null, /** 正在连接的设备 */ connecting: null, serviceRule: /^0000FFE0/, serviceId: null, characteristicRule: /^0000FFE1/, characteristicId: null, /** 正在执行的命令 */ command: null, responseResolve: null, responseReject: null, responseTimer: null, /** 是否显示蓝牙调试信息 */ debug: true, //搜索到的设备 devices: [], //取色仪主动返回的数据 deviceLab: null, } export default (props) => { let refStatus = useRef(stateObj) let [state, setState] = useState(refStatus.current) const changeStatus = (obj: Object): void => { refStatus.current = { ...refStatus.current, ...obj } setState({ ...refStatus.current }) } const init = async () => { try { await openAdapter() } catch (e) { changeStatus({ available: false }) } // 绑定事件通知 Taro.onBluetoothAdapterStateChange((res) => { emit({ type: 'stateUpdate', detail: res }) }) Taro.onBLEConnectionStateChange((res) => { emit({ type: res.connected ? 'connected' : 'disconnect', detail: res }) }) Taro.onBLECharacteristicValueChange(({ value }) => notifySubscriber(value)) subscribe(async (ev) => { if (ev.type === 'stateUpdate') { // 蓝牙状态发生的变化 changeStatus({ discovering: ev.detail.discovering, available: ev.detail.available }) } else if (ev.type === 'disconnect' && refStatus.current.connected && refStatus.current.connected.deviceId === ev.detail.deviceId) { // 断开连接 changeStatus({ connected: null, serviceId: null, characteristicId: null, deviceLab: null, devices: [], }) Taro.showToast({ icon: 'none', title: '蓝牙连接已断开' }) } else if (ev.type === 'connected' && refStatus.current.connecting) { // 连接成功 changeStatus({ connected: refStatus.current.connecting, connecting: null }) Taro.showToast({ title: '蓝牙已连接' }) } else if (ev.type === 'measure') { //监听取色仪主动推送lab await measureAndGetLab() } }) } /** 打开蓝牙适配器 */ const openAdapter = () => { return new Promise((resolve, reject) => { Taro.openBluetoothAdapter({ success: resolve, fail: reject, }) }) } /** * 推送事件 * @param {{type: string; data: any}} event */ const emit = (event) => { refStatus.current.listeners.forEach((cb) => { cb && cb(event) }) } const subscribe = (cb) => { if (cb) { changeStatus({ listeners: refStatus.current.listeners.add(cb), }) } } /** * 获取蓝牙适配器状态 * @returns {Promise<{discovering: boolean; available: boolean}>} */ const getAdapterState = () => { return new Promise((resolve, reject) => { Taro.getBluetoothAdapterState({ success: resolve, fail: reject, }) }) } /** * 启动设备扫描 * @param {(res: { devices: { name: string, deviceId: string, RSSI: number }[] }) => void} cb * @param {number} duration */ const startScan = (duration = 30000) => { console.log('开始寻找') changeStatus({ devices: [] }) Taro.onBluetoothDeviceFound(getDevices) return new Promise((resolve, reject) => { Taro.startBluetoothDevicesDiscovery({ allowDuplicatesKey: true, success: resolve, fail: reject, }) if (duration > 0) { setTimeout(() => { Taro.offBluetoothDeviceFound(getDevices) Taro.stopBluetoothDevicesDiscovery() console.log('停止搜索') }, duration) } }) } //获取搜索到的设备 const getDevices = (res) => { res.devices.forEach((device) => { // 排除掉已搜索到的设备和名称不合法的设备, 将新发现的设备添加到列表中 if (/^CM/.test(device.name) && !refStatus.current.devices.find((i) => i.deviceId === device.deviceId)) { changeStatus({ devices: [...refStatus.current.devices, device] }) } }) } /** * 连接设备 * @param {{ name: string, deviceId: string, RSSI: number }} device */ const connect = async (device) => { try { changeStatus({ connecting: device }) console.log('connecting::', device) await createConnection(device.deviceId) await discoverService(device.deviceId) await discoverCharacteristic(device.deviceId) await notifyCharacteristicValueChange(device.deviceId) } catch (e) { changeStatus({ connecting: null }) Taro.showToast({ icon: 'none', title: '蓝牙连接失败' }) throw e } } /** 断开当前连接的设备 */ const disconnect = async () => { if (!refStatus.current.connected && !refStatus.current.connecting) return if (refStatus.current.connected) { await closeConnection(refStatus.current.connected.deviceId) resetCommand() changeStatus({ connected: null, serviceId: null, characteristicId: null, devices: [], deviceLab: null, }) } if (refStatus.current.connecting) { await closeConnection(refStatus.current.connecting.deviceId) changeStatus({ connecting: null }) } } /** 创建 BLE 连接 */ function createConnection(deviceId) { return new Promise((resolve, reject) => { Taro.createBLEConnection({ deviceId, timeout: 2000, success: resolve, fail: reject, }) }) } /** 关闭 BLE 连接 */ function closeConnection(deviceId) { return new Promise((resolve, reject) => { Taro.closeBLEConnection({ deviceId, success: resolve, fail: reject, }) }) } /** 搜索服务 */ function discoverService(deviceId) { return new Promise((resolve, reject) => { Taro.getBLEDeviceServices({ deviceId, success: ({ services }) => { const service = services.find((i) => refStatus.current.serviceRule.test(i.uuid)) if (!service) { reject(new Error('服务不可用')) } else { changeStatus({ serviceId: service.uuid }) resolve(service) } }, fail: reject, }) }) } /** 搜索特征 */ function discoverCharacteristic(deviceId) { return new Promise((resolve, reject) => { Taro.getBLEDeviceCharacteristics({ deviceId, serviceId: refStatus.current.serviceId, success: ({ characteristics }) => { const characteristic = characteristics.find((i) => refStatus.current.characteristicRule.test(i.uuid)) if (!characteristic) { reject(new Error('特征不可用')) } else { changeStatus({ characteristicId: characteristic.uuid }) resolve(characteristic) } }, fail: reject, }) }) } /** 启动特征通知 */ function notifyCharacteristicValueChange(deviceId, stateParm = true) { return new Promise((resolve, reject) => { Taro.notifyBLECharacteristicValueChange({ deviceId, serviceId: refStatus.current.serviceId, characteristicId: refStatus.current.characteristicId, state: stateParm, success: resolve, fail: reject, }) }) } /** * 通知订阅器 * @param {ArrayBuffer} buffer */ function notifySubscriber(buffer) { if (refStatus.current.command) { if (refStatus.current.debug) { console.log(`[BLE RESP] ${uint8ArrayToHex(new Uint8Array(buffer))}`) } refStatus.current.command.fillResponse(buffer) if (refStatus.current.command.isComplete) { if (refStatus.current.command.isValid && refStatus.current.responseResolve) { refStatus.current.responseResolve(refStatus.current.command.response) } else if (!refStatus.current.command.isValid) { refStatus.current.responseReject(new Error('无效数据')) } resetCommand() } } else { const uint8Array = new Uint8Array(buffer) if (uint8Array[0] === 0xbb && uint8Array[1] === 1 && uint8Array[3] === 0) { const ev = { type: 'measure', detail: { mode: uint8Array[2] } } emit(ev) } } } /** * 发送命令 * @param {Command}} command * @returns {Promise} */ function exec(command) { return new Promise(async (resolve, reject) => { if (refStatus.current.command) { reject(new Error('正在执行其他命令')) } else { try { refStatus.current.command = command const data = command.data for (let i = 0; i < data.length; i++) { await sendData(data[i]) } if (command.responseSize <= 0) { resolve(true) resetCommand() } else { refStatus.current.responseReject = reject refStatus.current.responseResolve = resolve refStatus.current.responseTimer = setTimeout(() => { reject(new Error('命令响应超时')) resetCommand() }, command.timeout) } } catch (e) { reject(e) } } }) } /** * 发送命令 * @param {ArrayBuffer} buffer */ function sendData(buffer) { if (refStatus.current.debug) { console.log(`[BLE SEND] ${uint8ArrayToHex(new Uint8Array(buffer))}`) } return new Promise((resolve, reject) => { console.log('current:::', refStatus.current) Taro.writeBLECharacteristicValue({ deviceId: refStatus.current.connected.deviceId, serviceId: refStatus.current.serviceId, characteristicId: refStatus.current.characteristicId, value: buffer, success: resolve, fail: reject, }) }) } function resetCommand() { if (refStatus.current.responseTimer) { clearTimeout(refStatus.current.responseTimer) } changeStatus({ command: null, responseResolve: null, responseReject: null, responseTimer: null, }) } /** * 测量 * @param {number} mode * @returns {Promise} */ async function measure(mode = 0) { console.log('current1:::', Command.WakeUp) await exec(Command.WakeUp) console.log('current2:::', Command.WakeUp) await waitFor(50) console.log('current3:::', Command.WakeUp) return await exec(Command.measure(mode)) } /** * 获取测量的 lab 值 * @param {number} mode * @returns {Promise<{ L: number, a: number, b: number }>} */ async function getLab(mode = 0) { await exec(Command.WakeUp) await waitFor(50) const data: any = await exec(Command.getLab(mode)) return { L: uint8ArrayToFloat32(data.slice(5, 9)), a: uint8ArrayToFloat32(data.slice(9, 13)), b: uint8ArrayToFloat32(data.slice(13, 17)), } } /** * 测量并获取 lab 值 * @param {number} mode * @returns {Promise<{L: number, a: number, b: number}>} */ async function measureAndGetLab(mode = 0) { await measure(mode) await waitFor(50) const lab = await getLab(mode) console.log('lab2::', lab) changeStatus({ deviceLab: lab }) return lab } return ( ) } export const useBluetooth = () => { const res = React.useContext(Context) if (res) { return { ...res } } else { return {} } }