From 4a7f7b316f07c8c71e850606c122b72a86b45e58 Mon Sep 17 00:00:00 2001
From: czm <2192718639@qq.com>
Date: Mon, 18 Apr 2022 18:47:22 +0800
Subject: [PATCH] =?UTF-8?q?=E9=AB=98=E7=BA=A7=E6=90=9C=E7=B4=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app.tsx | 31 +-
src/common/bluetooth/color/colorDiff.js | 86 ++++
src/common/bluetooth/color/colorSpace.js | 13 +
src/common/bluetooth/color/lab.js | 54 ++
src/common/bluetooth/color/rgb.js | 9 +
src/common/bluetooth/color/xyz.js | 138 +++++
src/common/bluetooth/command.js | 146 ++++++
src/common/bluetooth/utils.js | 70 +++
src/components/bluetooth/LinkBlueTooth.tsx | 90 ++++
src/components/bluetooth/Popup.tsx | 73 +++
.../bluetooth/css/linkBlueTooth.module.scss | 30 ++
.../bluetooth/css/popup.module.scss | 90 ++++
src/components/search/index.module.scss | 3 +-
src/components/searchInput/index.module.scss | 22 +-
src/components/searchInput/index.tsx | 11 +-
src/components/tabs/index.module.scss | 24 +-
src/components/tabs/index.tsx | 9 +-
src/pages/classList/index.module.scss | 15 +-
src/pages/classList/index.tsx | 25 +-
.../components/selectData/index.module.scss | 49 ++
.../components/selectData/index.tsx | 57 +++
.../searchList/hightSearchList.module.scss | 36 +-
src/pages/searchList/hightSearchList.tsx | 57 ++-
src/pages/searchList/searchList.module.scss | 18 +
src/pages/searchList/searchList.tsx | 35 +-
src/use/contextBlueTooth.tsx | 477 ++++++++++++++++++
26 files changed, 1584 insertions(+), 84 deletions(-)
create mode 100644 src/common/bluetooth/color/colorDiff.js
create mode 100644 src/common/bluetooth/color/colorSpace.js
create mode 100644 src/common/bluetooth/color/lab.js
create mode 100644 src/common/bluetooth/color/rgb.js
create mode 100644 src/common/bluetooth/color/xyz.js
create mode 100644 src/common/bluetooth/command.js
create mode 100644 src/common/bluetooth/utils.js
create mode 100644 src/components/bluetooth/LinkBlueTooth.tsx
create mode 100644 src/components/bluetooth/Popup.tsx
create mode 100644 src/components/bluetooth/css/linkBlueTooth.module.scss
create mode 100644 src/components/bluetooth/css/popup.module.scss
create mode 100644 src/pages/searchList/components/selectData/index.module.scss
create mode 100644 src/pages/searchList/components/selectData/index.tsx
create mode 100644 src/use/contextBlueTooth.tsx
diff --git a/src/app.tsx b/src/app.tsx
index c9fd660..dc241a4 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -1,13 +1,26 @@
-import { MovableArea, View } from '@tarojs/components'
+import { Component } from 'react'
+import ContextBlueTooth from "@/use/contextBlueTooth"
+
import './app.scss'
+class App extends Component {
-const App = ({ children }) => {
- return (
- <>
- {children}
- >
- )
-}
+ componentDidMount () {}
-export default App
+ componentDidShow () {}
+
+ componentDidHide () {}
+
+ componentDidCatchError () {}
+
+ // this.props.children 是将要会渲染的页面
+ render () {
+ return (
+
+ {this.props.children}
+
+ )
+ }
+}
+
+export default App
\ No newline at end of file
diff --git a/src/common/bluetooth/color/colorDiff.js b/src/common/bluetooth/color/colorDiff.js
new file mode 100644
index 0000000..73e380d
--- /dev/null
+++ b/src/common/bluetooth/color/colorDiff.js
@@ -0,0 +1,86 @@
+module.exports = function(lab1, lab2){
+
+ var rgb2labArray1 = lab1;
+ var rgb2labArray2 = lab2;
+
+ var l1 = rgb2labArray1[0];
+ var a1 = rgb2labArray1[1];
+ var b1 = rgb2labArray1[2];
+
+ var l2 = rgb2labArray2[0];
+ var a2 = rgb2labArray2[1];
+ var b2 = rgb2labArray2[2];
+
+
+ var avg_lp = (l1 + l2) / 2;
+ var c1 = Math.sqrt(Math.pow(a1, 2) + Math.pow(b1, 2));
+ var c2 = Math.sqrt(Math.pow(a2, 2) + Math.pow(b2, 2));
+ var avg_c = (c1 + c2) / 2;
+ var g = (1- Math.sqrt(Math.pow(avg_c, 7) / (Math.pow(avg_c, 7) + Math.pow(25, 7)))) / 2;
+
+ var a1p = a1 * (1 + g);
+ var a2p = a2 * (1 + g);
+
+ var c1p = Math.sqrt(Math.pow(a1p, 2) + Math.pow(b1, 2));
+ var c2p = Math.sqrt(Math.pow(a2p, 2) + Math.pow(b2, 2));
+
+ var avg_cp = (c1p + c2p) / 2;
+
+ var h1p = rad2deg(Math.atan2(b1, a1p));
+ if(h1p < 0){
+
+ h1p = h1p + 360;
+ }
+
+ var h2p = rad2deg(Math.atan2(b2, a2p));
+ if(h2p < 0){
+
+ h2p = h2p + 360;
+ }
+
+ var avg_hp = Math.abs(h1p - h2p) > 180 ? (h1p + h2p + 360) / 2 : (h1p + h1p) / 2;
+
+ var t = 1 - 0.17 * Math.cos(deg2rad(avg_hp - 30)) + 0.24 * Math.cos(deg2rad(2 * avg_hp)) + 0.32 * Math.cos(deg2rad(3 * avg_hp + 6)) - 0.2 * Math.cos(deg2rad(4 * avg_hp - 63))
+
+ var delta_hp = h2p - h1p;
+ if(Math.abs(delta_hp) > 180){
+ if (h2p <= h1p) {
+ delta_hp += 360;
+ }
+ else {
+ delta_hp -= 360;
+ }
+ }
+
+ var delta_lp = l2 - l1;
+ var delta_cp = c2p - c1p;
+
+ delta_hp = 2 * Math.sqrt(c1p * c2p) * Math.sin(deg2rad(delta_hp) / 2);
+
+ var s_l = 1 + ((0.015 * Math.pow(avg_lp - 50, 2)) / Math.sqrt(20 + Math.pow(avg_lp - 50, 2)));
+ var s_c = 1 + 0.045 * avg_cp
+ var s_h = 1 + 0.015 * avg_cp * t;
+
+ var delta_ro = 30 * Math.exp( - (Math.pow((avg_hp - 275) / 25, 2)));
+ var r_c = 2 * Math.sqrt(Math.pow(avg_cp, 7) / (Math.pow(avg_cp, 7) + Math.pow(25, 7)));
+ var r_t = -r_c * Math.sin(2 * deg2rad(delta_ro));
+
+ var kl = 1, kc =1, kh = 1;
+
+ var delta_e = Math.sqrt(Math.pow(delta_lp / (kl * s_l), 2) + Math.pow(delta_cp / (kc * s_c), 2) + Math.pow(delta_hp / (kh * s_h), 2) + r_t * (delta_cp / (kc * s_c)) * (delta_hp / (kh * s_h)))
+
+ return delta_e
+
+
+ function rad2deg(rad){
+
+ return 360 * rad / (2 * Math.PI);
+ }
+ function deg2rad(deg){
+
+ return (2 * Math.PI * deg) / 360;
+ }
+}
+
+
+
diff --git a/src/common/bluetooth/color/colorSpace.js b/src/common/bluetooth/color/colorSpace.js
new file mode 100644
index 0000000..8d05344
--- /dev/null
+++ b/src/common/bluetooth/color/colorSpace.js
@@ -0,0 +1,13 @@
+
+import LabCom from './lab'
+import XyzCom from './xyz'
+import ColorDiff from './colorDiff'
+
+export const toRgb = (lab) => {
+ let xyz = LabCom.xyz(lab)
+ return XyzCom.rgb(xyz)
+}
+
+export const Ediff = (lab1, lab2) => {
+ return ColorDiff(lab1, lab2)
+}
\ No newline at end of file
diff --git a/src/common/bluetooth/color/lab.js b/src/common/bluetooth/color/lab.js
new file mode 100644
index 0000000..b4c0eab
--- /dev/null
+++ b/src/common/bluetooth/color/lab.js
@@ -0,0 +1,54 @@
+
+var xyz = require('./xyz');
+
+module.exports = {
+ name: 'lab',
+ min: [0,-100,-100],
+ max: [100,100,100],
+ channel: ['lightness', 'a', 'b'],
+ alias: ['LAB', 'cielab'],
+
+ xyz: function(lab) {
+ var l = lab[0],
+ a = lab[1],
+ b = lab[2],
+ x, y, z, y2;
+
+ if (l <= 8) {
+ y = (l * 100) / 903.3;
+ y2 = (7.787 * (y / 100)) + (16 / 116);
+ } else {
+ y = 100 * Math.pow((l + 16) / 116, 3);
+ y2 = Math.pow(y / 100, 1/3);
+ }
+
+ x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
+
+ z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
+
+ return [x, y, z];
+ }
+};
+
+
+//extend xyz
+xyz.lab = function(xyz){
+ var x = xyz[0],
+ y = xyz[1],
+ z = xyz[2],
+ l, a, b;
+
+ x /= 95.047;
+ y /= 100;
+ z /= 108.883;
+
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
+
+ l = (116 * y) - 16;
+ a = 500 * (x - y);
+ b = 200 * (y - z);
+
+ return [l, a, b];
+};
diff --git a/src/common/bluetooth/color/rgb.js b/src/common/bluetooth/color/rgb.js
new file mode 100644
index 0000000..60ff577
--- /dev/null
+++ b/src/common/bluetooth/color/rgb.js
@@ -0,0 +1,9 @@
+
+
+module.exports = {
+ name: 'rgb',
+ min: [0,0,0],
+ max: [255,255,255],
+ channel: ['red', 'green', 'blue'],
+ alias: ['RGB']
+};
diff --git a/src/common/bluetooth/color/xyz.js b/src/common/bluetooth/color/xyz.js
new file mode 100644
index 0000000..c6e2bee
--- /dev/null
+++ b/src/common/bluetooth/color/xyz.js
@@ -0,0 +1,138 @@
+
+var rgb = require('./rgb');
+
+var xyz = {
+ name: 'xyz',
+ min: [0,0,0],
+ channel: ['X','Y','Z'],
+ alias: ['XYZ', 'ciexyz', 'cie1931']
+};
+
+
+/**
+ * Whitepoint reference values with observer/illuminant
+ *
+ * http://en.wikipedia.org/wiki/Standard_illuminant
+ */
+xyz.whitepoint = {
+ //1931 2°
+ 2: {
+ //incadescent
+ A:[109.85, 100, 35.585],
+ // B:[],
+ C: [98.074, 100, 118.232],
+ D50: [96.422, 100, 82.521],
+ D55: [95.682, 100, 92.149],
+ //daylight
+ D65: [95.045592705167, 100, 108.9057750759878],
+ D75: [94.972, 100, 122.638],
+ //flourescent
+ // F1: [],
+ F2: [99.187, 100, 67.395],
+ // F3: [],
+ // F4: [],
+ // F5: [],
+ // F6:[],
+ F7: [95.044, 100, 108.755],
+ // F8: [],
+ // F9: [],
+ // F10: [],
+ F11: [100.966, 100, 64.370],
+ // F12: [],
+ E: [100,100,100]
+ },
+
+ //1964 10°
+ 10: {
+ //incadescent
+ A:[111.144, 100, 35.200],
+ C: [97.285, 100, 116.145],
+ D50: [96.720, 100, 81.427],
+ D55: [95.799, 100, 90.926],
+ //daylight
+ D65: [94.811, 100, 107.304],
+ D75: [94.416, 100, 120.641],
+ //flourescent
+ F2: [103.280, 100, 69.026],
+ F7: [95.792, 100, 107.687],
+ F11: [103.866, 100, 65.627],
+ E: [100,100,100]
+ }
+};
+
+
+/**
+ * Top values are the whitepoint’s top values, default are D65
+ */
+xyz.max = xyz.whitepoint[2].D65;
+
+
+/**
+ * Transform xyz to rgb
+ *
+ * @param {Array} xyz Array of xyz values
+ *
+ * @return {Array} RGB values
+ */
+xyz.rgb = function (_xyz, white) {
+ //FIXME: make sure we have to divide like this. Probably we have to replace matrix as well then
+ white = white || xyz.whitepoint[2].E;
+
+ var x = _xyz[0] / white[0],
+ y = _xyz[1] / white[1],
+ z = _xyz[2] / white[2],
+ r, g, b;
+
+ // assume sRGB
+ // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
+ r = (x * 3.240969941904521) + (y * -1.537383177570093) + (z * -0.498610760293);
+ g = (x * -0.96924363628087) + (y * 1.87596750150772) + (z * 0.041555057407175);
+ b = (x * 0.055630079696993) + (y * -0.20397695888897) + (z * 1.056971514242878);
+
+ r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
+ : r = (r * 12.92);
+
+ g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
+ : g = (g * 12.92);
+
+ b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
+ : b = (b * 12.92);
+
+ r = Math.min(Math.max(0, r), 1);
+ g = Math.min(Math.max(0, g), 1);
+ b = Math.min(Math.max(0, b), 1);
+
+ return [r * 255, g * 255, b * 255];
+}
+
+
+
+/**
+ * RGB to XYZ
+ *
+ * @param {Array} rgb RGB channels
+ *
+ * @return {Array} XYZ channels
+ */
+rgb.xyz = function(rgb, white) {
+ var r = rgb[0] / 255,
+ g = rgb[1] / 255,
+ b = rgb[2] / 255;
+
+ // assume sRGB
+ r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
+ g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
+ b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
+
+ var x = (r * 0.41239079926595) + (g * 0.35758433938387) + (b * 0.18048078840183);
+ var y = (r * 0.21263900587151) + (g * 0.71516867876775) + (b * 0.072192315360733);
+ var z = (r * 0.019330818715591) + (g * 0.11919477979462) + (b * 0.95053215224966);
+
+ white = white || xyz.whitepoint[2].E;
+
+ return [x * white[0], y * white[1], z * white[2]];
+};
+
+
+
+module.exports = xyz;
diff --git a/src/common/bluetooth/command.js b/src/common/bluetooth/command.js
new file mode 100644
index 0000000..97292d0
--- /dev/null
+++ b/src/common/bluetooth/command.js
@@ -0,0 +1,146 @@
+import { uint32ToUint8Array, uint8ArrayToHex } from "./utils";
+
+export class Command {
+ // 测量序号
+ static measureId = 1;
+
+ // 命令完整响应的长度
+ responseSize = 0;
+ // 命令发送的数据
+ content = new Uint8Array(0);
+ // 命令响应的数据
+ response = new Uint8Array(0);
+ // 等待响应的超时时间
+ timeout = 3000;
+ // 发送的数据是否需要生成和校验值
+ needSign = true;
+
+
+ /**
+ * @param {Uint8Array|ArrayBuffer|number[]} content
+ * @param {number} responseSize
+ * @param {number} timeout
+ * @param {boolean} needSign
+ */
+ constructor(content, responseSize, timeout = 3000, needSign = true) {
+ if (content instanceof Uint8Array) {
+ this.content = content;
+ } else {
+ this.content = new Uint8Array(content);
+ }
+ this.responseSize = responseSize;
+ if (typeof timeout === 'number' && timeout >= 0) {
+ this.timeout = timeout;
+ }
+ this.needSign = needSign;
+ }
+
+ /**
+ * 返回一个 ArrayBuffer 数组, 用于发送
+ * @returns {ArrayBuffer[]}
+ */
+ get data() {
+ if (this.content.length === 0) throw new Error('正文内容不能为空');
+ const data = [];
+ const b = new Uint8Array(this.content.buffer);
+ if (this.needSign) {
+ b[b.length - 1] = Command.getSign(b);
+ }
+ for (let i = 0; i < b.length; i += 20) {
+ data.push(b.slice(i, i + 20).buffer);
+ }
+ return data;
+ }
+
+ /** 是否接收完成 */
+ get isComplete() {
+ return this.response.length >= this.responseSize;
+ }
+
+ /** 是否有效 */
+ get isValid() {
+ return Command.getSign(this.response) === this.response[this.response.length - 1];
+ }
+
+ /**
+ * 填充响应数组
+ * @param {ArrayBuffer} buffer
+ */
+ fillResponse(buffer) {
+ this.response = new Uint8Array([...this.response, ...(new Uint8Array(buffer))]);
+ }
+
+
+ /**
+ * 获取和校验值
+ * @param {ArrayBuffer|Uint8Array} buffer
+ */
+ static getSign(buffer) {
+ const _b = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
+ let sum = 0;
+ _b.slice(0, _b.length - 1).forEach(i => sum += i);
+ return new Uint8Array([sum])[0];
+ }
+
+ // 唤醒命令
+ static WakeUp = new Command([0xf0], 0, 0, false);
+
+ /**
+ * 获取测量命令
+ * @param {number} mode
+ */
+ static measure(mode = 0) {
+ Command.measureId += 1;
+ const measureId = uint32ToUint8Array(Command.measureId);
+ return new Command([0xbb, 1, mode, ...measureId, 0, 0xff, 0], 10, 1500);
+ }
+
+ /**
+ * 获取测量数据 (Lab)
+ * @param {number} mode
+ */
+ static getLab(mode = 0) {
+ return new Command([0xbb, 3, mode, 0, 0, 0, 0, 0, 0xff, 0], 20, 1500);
+ }
+
+ /**
+ * 获取测量数据 (RGB)
+ * @param {number} mode
+ */
+ static getRGB(mode = 0) {
+ return new Command([0xbb, 4, mode, 0, 0, 0, 0, 0, 0xff, 0], 20, 1500);
+ }
+
+ /**
+ * 获取测量的光谱数据
+ * @param {number} mode
+ */
+ static getSpectral(mode = 0) {
+ return new Command([0xbb, 2, 0x10 + mode, 0, 0, 0 ,0 ,0, 0xff, 0], 200, 5000);
+ }
+
+ /**
+ * 白校准
+ * @param {number} check 是否判断校准成功 1 判断 0 不判断
+ */
+ static whiteCalibrate(check = 1) {
+ return new Command([0xbb, 0x11, check, 0, 0, 0, 0, 0, 0xff, 0], 10, 1500);
+ }
+
+
+ /**
+ * 黑校准
+ * @param {number} check 是否判断校准成功
+ */
+ static blackCalibrate(check = 1) {
+ return new Command([0xbb, 0x10, check, 0, 0, 0, 0, 0, 0xff, 0], 10, 1500);
+ }
+
+
+ /** 获取校准状态 */
+ static GetCalibrationInf = new Command([0xbb, 0x1e, 0, 0, 0, 0, 0, 0, 0xff, 0], 20, 1500);
+
+
+
+ static GetDeviceInf = new Command([0xbb, 0x12, 0x01, 0, 0, 0, 0, 0, 0xff, 0], 200, 5000);
+}
\ No newline at end of file
diff --git a/src/common/bluetooth/utils.js b/src/common/bluetooth/utils.js
new file mode 100644
index 0000000..9c4b018
--- /dev/null
+++ b/src/common/bluetooth/utils.js
@@ -0,0 +1,70 @@
+/**
+ * Uint32 转 Uint8 数组
+ * @param {number} n
+ */
+export function uint32ToUint8Array(n) {
+ return new Uint8Array(new Uint32Array([n]).buffer);
+}
+
+/**
+ * Uint8 数组 转 Float32
+ * @param {Uint8Array} raw
+ */
+export function uint8ArrayToFloat32(raw) {
+ return new Float32Array(raw.buffer)[0];
+}
+
+
+/**
+ * Uint8 数组 转 Uint16
+ * @param {Uint8Array} raw
+ */
+export function uint8ArrayToUint16(raw) {
+ return new Uint16Array(raw.buffer)[0];
+}
+
+
+/**
+ * Uint8 数组转 Uint32
+ * @param {Uint8Array} raw
+ * @returns
+ */
+export function uint8ArrayToUnit32(raw) {
+ return new Uint32Array(raw.buffer)[0];
+}
+
+
+/**
+ * 等待指定时长
+ * @param {number} duration
+ */
+export function waitFor(duration) {
+ return new Promise(resolve => {
+ setTimeout(resolve, duration);
+ });
+}
+
+
+/**
+ * uint8 数组转 hex 字符串
+ * @param {Uint8Array} raw
+ */
+export function uint8ArrayToHex(raw) {
+ const s = [];
+ raw.forEach(i => {
+ const b = i.toString(16);
+ s.push(b.length > 1 ? b : `0${b}`);
+ });
+ return s.join(' ');
+}
+
+
+// 二进制转字符串(ascii)
+export function bufferToString(buffer) {
+ let str = "";
+ for (let code of buffer) {
+ if (code === 0) break;
+ str += utf82string(code);
+ }
+ return str;
+}
\ No newline at end of file
diff --git a/src/components/bluetooth/LinkBlueTooth.tsx b/src/components/bluetooth/LinkBlueTooth.tsx
new file mode 100644
index 0000000..66f01a7
--- /dev/null
+++ b/src/components/bluetooth/LinkBlueTooth.tsx
@@ -0,0 +1,90 @@
+import { View } from "@tarojs/components";
+import { memo, useEffect, useMemo, useState } from "react";
+import Taro from "@tarojs/taro";
+import {useBluetooth} from "@/use/contextBlueTooth"
+import SearchInput from "@/components/searchInput";
+import Popup from "@/components/bluetooth/Popup"
+import classnames from "classnames";
+import styles from "./css/linkBlueTooth.module.scss"
+
+export default memo(() => {
+ const {state, init, startScan, connect, disconnect} = useBluetooth()
+
+ useEffect(() => {
+ init()
+ }, [])
+
+ const [linkStatus, setLinkStatus] = useState(1)
+ useEffect(() => {
+ if(!state.available) {
+ setLinkStatus(1)
+ } else if(state.available&&state.connected?.name) {
+ setLinkStatus(3)
+ } else {
+ setLinkStatus(2)
+ }
+ console.log('aaa:::',state.connected)
+ }, [state.available, state.connected])
+
+ const linkName = useMemo(() => {
+ return state.connected?.localName||''
+ }, [state.connected])
+
+ //链接设备
+ const onLinkListen = (item) => {
+ if(!state.connected&&!state.connecting)
+ connect(item)
+ }
+
+ const [popupShow, setPopupShow] = useState(false)
+ //显示设备列表
+ const onFindDevice = () => {
+ if(linkStatus == 1) {
+ Taro.showToast({
+ title:'请打开蓝牙',
+ icon:'none'
+ })
+ } else {
+ setPopupShow(true)
+ onFindEven()
+ }
+
+ }
+ const onFindEven = () => {
+ if(!state.discovering&&!state.connected&&!state.connecting)
+ startScan()
+ }
+
+ //断开链接
+ const onDisconnect = () => {
+ disconnect()
+ setPopupShow(false)
+ }
+
+ return (
+ <>
+
+
+
+
+ {
+ linkStatus == 1&&请开启蓝牙||
+ linkStatus == 2&&未连接设备||
+ linkStatus == 3&&{linkName}
+ }
+
+
+ setPopupShow(false)}
+ onLink={item => onLinkListen(item)}
+ onOff={onDisconnect}
+ onFind={onFindEven}
+ />
+
+ >
+
+ );
+
+})
diff --git a/src/components/bluetooth/Popup.tsx b/src/components/bluetooth/Popup.tsx
new file mode 100644
index 0000000..8a5070a
--- /dev/null
+++ b/src/components/bluetooth/Popup.tsx
@@ -0,0 +1,73 @@
+import { ScrollView, View } from "@tarojs/components"
+import { memo, useEffect, useState } from "react"
+import Loading from "@/components/loading"
+import style from "./css/popup.module.scss"
+
+interface params {
+ state: any,
+ show: Boolean,
+ onClose: (Boolean) => void,
+ onLink: (any) => void,
+ children?: React.ReactNode
+ onOff: () => void,
+ onFind: () => void,
+}
+
+export default memo(({state, show=false, onClose, onLink, onOff, onFind}:params) => {
+ const [popupShow, setPopupShow] = useState(show)
+ useEffect(() => {
+ setPopupShow(show)
+ }, [show])
+ const onCloseListener = () => {
+ onClose(false)
+ }
+
+ return (
+ <>
+ {
+ popupShow&&
+
+ 搜索设备
+
+
+ {
+ (state.devices&&state.devices.length > 0)&&state?.devices.map(item => {
+ return (
+ onLink(item)}>
+ {item.name}
+ {
+ (!state.connecting&&!state.connected)&&链接||
+ (state.connecting&&item.deviceId == state.connecting.deviceId)&&正在链接...||
+ (state.connected&&item.deviceId == state.connected.deviceId)&&链接成功
+ }
+
+ )
+ })||
+
+ {
+ (!state.discovering)&& <>
+ 暂无设备,请按以下条件检查
+ 1.请确保取色仪处于激活状态
+ 2.请确保取色仪没有链接其他设备
+ 3.请打开手机定位
+ >||
+ 设备搜索中
+ }
+
+
+
+ }
+
+
+ {
+ state.connected&&断开链接||
+ (!state.connected&&state.discovering)&&搜索中||
+ 重新搜索
+ }
+
+
+
+ }
+ >
+ )
+})
\ No newline at end of file
diff --git a/src/components/bluetooth/css/linkBlueTooth.module.scss b/src/components/bluetooth/css/linkBlueTooth.module.scss
new file mode 100644
index 0000000..4156811
--- /dev/null
+++ b/src/components/bluetooth/css/linkBlueTooth.module.scss
@@ -0,0 +1,30 @@
+
+.main{
+ .bluetooth_link{
+ display: flex;
+ align-items: center;
+ .link_status{
+ width: 12px;
+ height: 12px;
+ background: #f02409;
+ border-radius: 50%;
+ }
+ .link_statused{
+ background: #07C160;
+ }
+ .link_statused_no{
+ background: #f0ec09;
+ }
+ .link_name{
+ font-size: $font_size;
+ margin-left: 20px;
+
+ }
+ .link_name_no{
+ color: #f02409;
+ }
+ .link_name_no_link{
+ color: #f0ec09;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/bluetooth/css/popup.module.scss b/src/components/bluetooth/css/popup.module.scss
new file mode 100644
index 0000000..04eecb7
--- /dev/null
+++ b/src/components/bluetooth/css/popup.module.scss
@@ -0,0 +1,90 @@
+.popup{
+ width: 100vw;
+ height: 100vh;
+ position: absolute;
+ top: 0;
+ left: 0;
+ .mask{
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.5);
+ position: fixed;
+ top:0;
+ left:0;
+ z-index: 9;
+ }
+ .content{
+ z-index: 99;
+ background-color: #fff;
+ width: 75vw;
+ height: 600px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ border-radius: 20px;
+ transform: translateX(-50%) translateY(-50%);
+ display: flex;
+ flex-direction: column;
+ font-size: 28px;
+ .title{
+ text-align: center;
+ margin: 20px;
+ }
+ .list{
+ height: 480px;
+ padding: 0 20px;
+ .scroll{
+ height: 100%;
+ }
+ .item{
+ margin-bottom: 20px;
+ display: flex;
+ justify-content: space-between;
+ border-bottom: 1px dashed #ccc;
+ padding: 15px 0;
+ color: #3b3b3b;
+ @mixin link{
+ font-size: 25px;
+ }
+ .link_success{
+ @include link;
+ color: green;
+ }
+ .link_ing {
+ color: orange;
+ }
+ }
+ .noDevice{
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ color: #a8a8a8;
+ .n_item{
+ width: 100%;
+ text-align: left;
+ margin-top: 20px;
+ padding: 0 30px;
+ box-sizing: border-box;
+ }
+ }
+
+ }
+ .footer{
+ text-align: center;
+ padding: 20px 0;
+ background-color: #f1f1f1;
+ border-radius: 0 0 10px 10px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+ .finding{
+ color: orange;
+ }
+ .footer_off{
+ color: red;
+ }
+ }
+}
diff --git a/src/components/search/index.module.scss b/src/components/search/index.module.scss
index 2121879..46a8c27 100644
--- a/src/components/search/index.module.scss
+++ b/src/components/search/index.module.scss
@@ -20,6 +20,7 @@
border-radius: 50px;
padding: 0 60px;
box-sizing: border-box;
+ z-index:0;
&::-webkit-input-placeholder { /* WebKit browsers */
color: #999;
font-size: 16px;
@@ -55,7 +56,7 @@
position: absolute;
left: 10px;
margin-right: 0;
-
+ z-index: 10;
}
.icon_out{
margin-right: 10px;
diff --git a/src/components/searchInput/index.module.scss b/src/components/searchInput/index.module.scss
index c77b507..c381191 100644
--- a/src/components/searchInput/index.module.scss
+++ b/src/components/searchInput/index.module.scss
@@ -4,20 +4,24 @@
align-items: center;
min-height: 62px;
padding: 10px 0;
- border-bottom: 1px solid #f3f3f3;
+ border-bottom: 1px solid #F0F0F0;
.searchInput_title {
width: 150px;
+ min-height: 50px;
font-size: 28px;
- border-right: 1px solid #f3f3f3;
- color: $color_font_on;
+ border-right: 1px solid #F0F0F0;
+ color: $color_font_two;
margin-right: 20px;
padding-left: 20px;
- &::before{
- content: "";
- height: 50px;
- width: 1px;
- background-color: #f3f3f3;
- }
+ display: flex;
+ align-items: center;
+
+ // &::before{
+ // content: "";
+ // height: 50px;
+ // width: 1px;
+ // background-color: #F0F0F0;
+ // }
}
.searchInput_con{
flex:1;
diff --git a/src/components/searchInput/index.tsx b/src/components/searchInput/index.tsx
index 2d826fd..35d507c 100644
--- a/src/components/searchInput/index.tsx
+++ b/src/components/searchInput/index.tsx
@@ -1,5 +1,5 @@
import { Input, View } from "@tarojs/components";
-import { memo, useDebugValue, useMemo } from "react";
+import { memo, ReactHTMLElement, ReactNode, useDebugValue, useMemo } from "react";
import styles from './index.module.scss';
type Params = {
@@ -10,7 +10,8 @@ type Params = {
showTitle?: false|true,
showBorder?: false|true,
changeOnInput?: (string) => void,
- clickOnInput?: () => void
+ clickOnInput?: () => void,
+ children?: ReactNode
}
@@ -37,9 +38,11 @@ export default memo((props: Params) => {
{showTitle&&{title}}
- clickOnInput?.()} onInput={(e) => changeOnInput?.(e.detail.value)}/>
+ {!props.children&& clickOnInput?.()} onInput={(e) => changeOnInput?.(e.detail.value)}/>
+ ||{props.children}
+ }
- {showIcon&&}
+ {showIcon&&}
)
})
\ No newline at end of file
diff --git a/src/components/tabs/index.module.scss b/src/components/tabs/index.module.scss
index c04b80f..2f0bc2d 100644
--- a/src/components/tabs/index.module.scss
+++ b/src/components/tabs/index.module.scss
@@ -1,20 +1,28 @@
.tabs_main{
display: flex;
+ width: 100%;
.tabs_scroll{
width: 100%;
display: flex;
white-space: nowrap;
- border-bottom: 1px solid $color_font_two;
- border-top: 1px solid $color_font_two;
- height: 102px;
-
+ ::-webkit-scrollbar {
+ display:none;
+ width:0;
+ height:0;
+ color:transparent;
+ }
.tabs_item{
flex:1;
display: inline-block;
- padding: 10px 20px;
- height: 100%;
- box-sizing: border-box;
- position: relative;
+ font-size: 24rpx;
+ background-color: #F0F0F0;
+ border-radius: 24rpx;
+ min-width: 126rpx;
+ height: 46.93rpx;
+ text-align: center;
+ line-height: 46.93rpx;
+ color: #707070;
+ margin-right: 20px;
.tabs_item_con{
height: 100%;
display: flex;
diff --git a/src/components/tabs/index.tsx b/src/components/tabs/index.tsx
index 71a1bc5..3c7492e 100644
--- a/src/components/tabs/index.tsx
+++ b/src/components/tabs/index.tsx
@@ -13,10 +13,12 @@ type Params = {
list?: ListProps[],
defaultValue?: number|string,
children?: ReactNode,
- tabsOnClick?: (ListProps) => void
+ tabsOnClick?: (ListProps) => void,
+ style?:Object,
+
}
-export default memo(({list = [], defaultValue = 0, tabsOnClick}: Params) => {
+export default memo(({list = [], defaultValue = 0, tabsOnClick, style={}}: Params) => {
const [selected, setSelected] = useState(defaultValue)
const [tabId, setTabId] = useState('')
@@ -46,8 +48,7 @@ export default memo(({list = [], defaultValue = 0, tabsOnClick}: Params) => {
list.map((item, index) => {
return (
clickEvent({item,index})}>
- {item.title}
- {(selected == item.value) && }
+ {item.title}
)
})
diff --git a/src/pages/classList/index.module.scss b/src/pages/classList/index.module.scss
index b8028e4..7b62533 100644
--- a/src/pages/classList/index.module.scss
+++ b/src/pages/classList/index.module.scss
@@ -15,6 +15,8 @@
color: $color_font_three;
.text_one{
color: $color_main;
+ display: flex;
+ align-items: center;
}
.text_two{
position: relative;
@@ -33,20 +35,11 @@
}
}
}
- .filter_btn{
+ .filter_btns{
display: flex;
justify-content: space-between;
padding: 20px;
- view{
- font-size: $font_size_medium;
- background-color: #F0F0F0;
- border-radius: 24px;
- width: 126px;
- height: 46.93px;
- text-align: center;
- line-height: 46.93px;
- color: $color_font_three;
- }
+
.selected{
background-color: #ecf5ff;
border: 2px solid #cde5ff;
diff --git a/src/pages/classList/index.tsx b/src/pages/classList/index.tsx
index f9a60b7..bb497cc 100644
--- a/src/pages/classList/index.tsx
+++ b/src/pages/classList/index.tsx
@@ -7,9 +7,22 @@ import Popup from "@/components/popup";
import styles from './index.module.scss'
import { useState } from "react";
import Filter from "./components/filter";
+import Tabs from "@/components/tabs";
+import SortBtn from "@/components/sortBtn";
export default () => {
const [showPopup, setShowPopup] = useState(false)
+ const [selectList, setSelectList] = useState([
+ {title: '系列', value:1},
+ {title: '系列', value:2},
+ {title: '系列', value:3},
+ {title: '系列', value:4},
+ {title: '系列', value:6},
+ {title: '系列', value:7},
+ {title: '系列', value:8},
+ {title: '系列', value:9},
+ {title: '系列', value:10},
+ ])
return (
@@ -17,17 +30,17 @@ export default () => {
- 综合
+
+ 综合
+
+
setShowPopup(true)}>
筛选
-
- 系列
- 幅宽
- 克重
- 成分
+
+
diff --git a/src/pages/searchList/components/selectData/index.module.scss b/src/pages/searchList/components/selectData/index.module.scss
new file mode 100644
index 0000000..2f0bc2d
--- /dev/null
+++ b/src/pages/searchList/components/selectData/index.module.scss
@@ -0,0 +1,49 @@
+.tabs_main{
+ display: flex;
+ width: 100%;
+ .tabs_scroll{
+ width: 100%;
+ display: flex;
+ white-space: nowrap;
+ ::-webkit-scrollbar {
+ display:none;
+ width:0;
+ height:0;
+ color:transparent;
+ }
+ .tabs_item{
+ flex:1;
+ display: inline-block;
+ font-size: 24rpx;
+ background-color: #F0F0F0;
+ border-radius: 24rpx;
+ min-width: 126rpx;
+ height: 46.93rpx;
+ text-align: center;
+ line-height: 46.93rpx;
+ color: #707070;
+ margin-right: 20px;
+ .tabs_item_con{
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: $font_size_medium;
+ }
+ .tabs_index{
+ height: 5px;
+ width: 100%;
+ background-color:$color_main;
+ position:absolute;
+ bottom: 0;
+ left:0;
+ border-radius: 50px;
+ }
+ .tabs_item_select{
+ color: $color_main;
+ }
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/pages/searchList/components/selectData/index.tsx b/src/pages/searchList/components/selectData/index.tsx
new file mode 100644
index 0000000..60db6ee
--- /dev/null
+++ b/src/pages/searchList/components/selectData/index.tsx
@@ -0,0 +1,57 @@
+import { ScrollView, View } from "@tarojs/components";
+import { memo, useState, ReactNode, useEffect } from "react";
+import classnames from "classnames";
+import styles from './index.module.scss'
+
+
+type ListProps = {
+ title: string,
+ value: number
+}
+
+type Params = {
+ list?: ListProps[],
+ defaultValue?: number|string,
+ children?: ReactNode,
+ tabsOnClick?: (ListProps) => void,
+
+}
+
+export default memo(({list = [], defaultValue = 0, tabsOnClick}: Params) => {
+
+ const [tabId, setTabId] = useState('')
+
+ useEffect(() => {
+ const index = list?.findIndex(item => {
+ return item.value == defaultValue
+ })
+ if(index !== -1) {
+ const num = index > 0?( index - 1) : 0
+ setTabId(list[num].value.toString())
+ }
+ }, [])
+
+ const clickEvent = ({item, index}: {item:ListProps, index:number}) => {
+ tabsOnClick?.(item)
+ setTabId(index.toString())
+ }
+ return (
+ <>
+
+
+
+ {
+ list.map((item, index) => {
+ return (
+ clickEvent({item,index})}>
+ {item.title}
+
+ )
+ })
+ }
+
+
+
+ >
+ )
+})
\ No newline at end of file
diff --git a/src/pages/searchList/hightSearchList.module.scss b/src/pages/searchList/hightSearchList.module.scss
index fe0e4de..b40fa10 100644
--- a/src/pages/searchList/hightSearchList.module.scss
+++ b/src/pages/searchList/hightSearchList.module.scss
@@ -5,13 +5,30 @@
background-color: $color_bg_one;
.search{
padding: 20px;
+ .SearchInput{
+ background-color: #fff;
+ padding: 10px 20px;
+ box-sizing: border-box;
+ border-radius: 10px;
+ }
+
+ .bluetooth_color{
+ .color_bock{
+ width: 100px;
+ height: 46px;
+ }
+ .color_bock_no{
+ font-size: $font_size_medium;
+ color: $color_font_three;
+ }
+ }
}
.filter{
.filter_all {
display: flex;
justify-content: space-between;
align-items: center;
- padding: 20px 50px;
+ padding: 20px 130px;
font-size: $font_size_medium;
color: $color_font_three;
.text_zh, .text_sc{
@@ -149,6 +166,23 @@
height: 224px;
background: #e5ad3a;
border-radius: 20px 20px 0px 0px;
+ position: relative;
+ image{
+ width: 100%;
+ height: 100%;
+ border-radius: 20px 20px 0px 0px;
+ }
+ .color_num {
+ background: rgba(0,0,0, 0.5);
+ border-radius: 0px 50px 0px 0px;
+ font-size: $font_size_min;
+ color: #fff;
+ position: absolute;
+ left:0;
+ bottom:0;
+ padding: 5px 20px;
+ box-sizing: border-box;
+ }
}
}
.product_info{
diff --git a/src/pages/searchList/hightSearchList.tsx b/src/pages/searchList/hightSearchList.tsx
index 8f77271..a11ec20 100644
--- a/src/pages/searchList/hightSearchList.tsx
+++ b/src/pages/searchList/hightSearchList.tsx
@@ -1,12 +1,17 @@
-import { ScrollView, Text, View } from "@tarojs/components"
+import { Image, ScrollView, Text, View } from "@tarojs/components"
import classnames from "classnames";
import Search from '@/components/search'
import Filter from "@/components/filter";
import InfiniteScroll from '@/components/infiniteScroll'
import SortBtn from "@/components/sortBtn";
+import SearchInput from "@/components/searchInput";
+import LinkBlueTooth from "@/components/bluetooth/LinkBlueTooth";
+import {useBluetooth} from "@/use/contextBlueTooth"
+import {toRgb} from '@/common/bluetooth/color/colorSpace'
import Tabs from "@/components/tabs";
import styles from './hightSearchList.module.scss'
-import { useCallback, useState } from "react";
+import { useCallback, useEffect, useState } from "react";
+import Taro, { useReady } from "@tarojs/taro";
export default () => {
const [showFilter, setShowFilter] = useState(false)
@@ -29,10 +34,40 @@ export default () => {
setScrollStatus(false)
}
},[])
+
+ const {state, measureAndGetLab} = useBluetooth()
+ const getLab = () => {
+ if(state.connected) {
+ measureAndGetLab()
+ } else {
+ Taro.showToast({
+ title: '请链接设备',
+ icon: 'none'
+ })
+ }
+ }
+
+ const [blueToothColor, setBlueToothColor] = useState('')
+ useEffect(() => {
+ if(state.deviceLab) {
+ console.log('颜色:',state.deviceLab)
+ const rgb = toRgb([state.deviceLab.L, state.deviceLab.a, state.deviceLab.b])
+ setBlueToothColor(`rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`)
+ }
+ }, [state.deviceLab])
return (
-
+
+
+
+ getLab()}>
+ {blueToothColor&&||
+ 请取色
+ }
+
+
+
@@ -44,10 +79,7 @@ export default () => {
收藏
-
- 高级搜索
-
-
+
@@ -76,14 +108,13 @@ export default () => {
{new Array(9).fill(' ').map(item => {
return
-
+
+
+ 25色
+
0770#21S精棉平纹
-
- 160cm
- 110g
-
- 67.6%棉24%涤纶6.4%氨纶
+ 平纹系列
})}
diff --git a/src/pages/searchList/searchList.module.scss b/src/pages/searchList/searchList.module.scss
index fe0e4de..11f55be 100644
--- a/src/pages/searchList/searchList.module.scss
+++ b/src/pages/searchList/searchList.module.scss
@@ -57,6 +57,7 @@
.filter_scroll{
flex:1;
width: 0;
+ padding-left: 20px;
::-webkit-scrollbar {
display:none;
width:0;
@@ -149,6 +150,23 @@
height: 224px;
background: #e5ad3a;
border-radius: 20px 20px 0px 0px;
+ position: relative;
+ image{
+ width: 100%;
+ height: 100%;
+ border-radius: 20px 20px 0px 0px;
+ }
+ .color_num {
+ background: rgba(0,0,0, 0.5);
+ border-radius: 50px 0px 0px 0px;
+ font-size: $font_size_min;
+ color: #fff;
+ position: absolute;
+ right:0;
+ bottom:0;
+ padding: 5px 20px;
+ box-sizing: border-box;
+ }
}
}
.product_info{
diff --git a/src/pages/searchList/searchList.tsx b/src/pages/searchList/searchList.tsx
index 2379d9c..21a5757 100644
--- a/src/pages/searchList/searchList.tsx
+++ b/src/pages/searchList/searchList.tsx
@@ -1,10 +1,12 @@
-import { ScrollView, Text, View } from "@tarojs/components"
+import { Image, ScrollView, Text, View } from "@tarojs/components"
import classnames from "classnames";
import Search from '@/components/search'
import Filter from "@/components/filter";
import InfiniteScroll from '@/components/infiniteScroll'
import SortBtn from "@/components/sortBtn";
+import SelectData from "./components/selectData";
import Tabs from "@/components/tabs";
+import { goLink } from "@/common/common";
import styles from './searchList.module.scss'
import { useCallback, useState } from "react";
@@ -15,11 +17,11 @@ export default () => {
{title: '系列', value:2},
{title: '系列', value:3},
{title: '系列', value:4},
- {title: '系列', value:5},
- {title: '系列', value:5},
- {title: '系列', value:5},
- {title: '系列', value:5},
- {title: '系列', value:5},
+ {title: '系列', value:6},
+ {title: '系列', value:7},
+ {title: '系列', value:8},
+ {title: '系列', value:9},
+ {title: '系列', value:10},
])
const [scrollStatus, setScrollStatus] = useState(false)
const onscroll = useCallback((e) => {
@@ -44,22 +46,16 @@ export default () => {
收藏
-
+ goLink('/pages/searchList/hightSearchList')}>
高级搜索
+
-
-
- 系列
- 幅宽
- 克重
- 克重
- 克重
- 成分
-
-
+
+
+
setShowFilter(true)}>
筛选
@@ -76,7 +72,10 @@ export default () => {
{new Array(9).fill(' ').map(item => {
return
-
+
+
+ 25色
+
0770#21S精棉平纹
diff --git a/src/use/contextBlueTooth.tsx b/src/use/contextBlueTooth.tsx
new file mode 100644
index 0000000..518c5cc
--- /dev/null
+++ b/src/use/contextBlueTooth.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 useBluetooth = () => {
+ const res = React.useContext(Context)
+ if(res) {
+ return {...res}
+ } else {
+ return {}
+ }
+
+}
\ No newline at end of file