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) interface stateStype { 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: stateStype = { /** 事件监听器 */ 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 {} } }