From 2de447402e27396aeb5796570ede338ad05a6d63 Mon Sep 17 00:00:00 2001 From: Haiyi <1021441632@qq.com> Date: Tue, 28 Jun 2022 14:04:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/sampleComparison/index.module.scss | 8 + src/pages/sampleComparison/index.tsx | 46 +- src/use/BlueToothCopy.tsx | 477 +++++++++++++++++++ 3 files changed, 522 insertions(+), 9 deletions(-) create mode 100644 src/use/BlueToothCopy.tsx diff --git a/src/pages/sampleComparison/index.module.scss b/src/pages/sampleComparison/index.module.scss index fd9517c..8ee7104 100644 --- a/src/pages/sampleComparison/index.module.scss +++ b/src/pages/sampleComparison/index.module.scss @@ -94,6 +94,7 @@ page { font-family: Microsoft YaHei, Microsoft YaHei-Regular; font-weight: 400; color: #707070; + } } @@ -101,6 +102,13 @@ page { .color_bock { width: 290px; height: 290px; + border-radius: 50%; + } + + .color_bocktwo { + width: 290px; + height: 290px; + border-radius: 50%; } .nameColor { diff --git a/src/pages/sampleComparison/index.tsx b/src/pages/sampleComparison/index.tsx index b8c9cf7..982e58c 100644 --- a/src/pages/sampleComparison/index.tsx +++ b/src/pages/sampleComparison/index.tsx @@ -3,6 +3,7 @@ import { Image, Text, Textarea, View } from "@tarojs/components" import Taro, { useDidShow, usePullDownRefresh, useRouter } from "@tarojs/taro"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useBluetooth } from "@/use/contextBlueTooth" +import { useBluetoothTwo } from "@/use/BlueToothCopy" import classnames from "classnames"; import LinkBlueTooth from "./compoents/bluetooth/LinkBlueTooth"; import { toRgb } from '@/common/bluetooth/color/colorSpace' @@ -21,10 +22,22 @@ export default () => { product_kind_id: '', component: '' }) + + const [colorList, setColorList] = useState({ + one: [], + two: [] + }) const { state: colorState, measureAndGetLab } = useBluetooth() - const getLab = () => { + + const getLab = async (val) => { if (colorState.connected) { - measureAndGetLab() + let res = await measureAndGetLab() + if (val === 1) { + setColorList({ ...colorList, one: res }) + } else { + setColorList({ ...colorList, two: res }) + console.log('colorList',colorList) + } } else { Taro.showToast({ title: '请链接设备', @@ -32,15 +45,24 @@ export default () => { }) } } + //监听lab数据变化 const [blueToothColor, setBlueToothColor] = useState('') + const [blueToothColorTwo, setBlueToothColorTwo] = useState('') useEffect(() => { if (colorState.deviceLab) { const rgb = toRgb([colorState.deviceLab.L, colorState.deviceLab.a, colorState.deviceLab.b]) - setBlueToothColor(`rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`) - setSearchField({ ...searchField, l: rgb[0], a: rgb[1], b: rgb[2], size: 10 }) + console.log(colorList, 'colorList.one.lengthcolorList.one.length') + if (colorList.one?.constructor === Object) { + setBlueToothColor(`rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`) + console.log(blueToothColor, 'blueToothColorblueToothColor') + } + if (colorList.two?.constructor === Object) { + setBlueToothColorTwo(`rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`) + } + // setSearchField({ ...searchField, l: rgb[0], a: rgb[1], b: rgb[2], size: 10 }) } - }, [colorState.deviceLab]) + }, [colorList]) return ( {/* @@ -58,7 +80,7 @@ export default () => { 对比样品 { blueToothColor === '' && - getLab()}> + getLab(1)}> 点击取色 } @@ -69,9 +91,15 @@ export default () => { 对比样品 - - getLab()}>点击取色 - + { + blueToothColorTwo === '' && + getLab(2)}> + 点击取色 + + } + {blueToothColorTwo && + + } -- diff --git a/src/use/BlueToothCopy.tsx b/src/use/BlueToothCopy.tsx new file mode 100644 index 0000000..0d07766 --- /dev/null +++ b/src/use/BlueToothCopy.tsx @@ -0,0 +1,477 @@ +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 useBluetoothTwo = () => { + const res = React.useContext(Context) + if(res) { + return {...res} + } else { + return {} + } + +} \ No newline at end of file