TestRestructureWechatLymark.../src/use/contextBlueTooth.tsx
2022-04-18 18:47:22 +08:00

477 lines
15 KiB
TypeScript

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<params|unknown>(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<Uint8Array>}
*/
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 <Context.Provider children={props.children} value={{
init,
state,
startScan,
measureAndGetLab,
getAdapterState,
connect,
disconnect
}} />
}
export const useBluetooth = () => {
const res = React.useContext<any>(Context)
if(res) {
return {...res}
} else {
return {}
}
}