Ver código fonte

ffeature:增加小程序蓝牙的底层库

zeng.chen 8 meses atrás
pai
commit
655f40a575

+ 38 - 0
devices/battery_util.js

@@ -0,0 +1,38 @@
+// 模拟 WatchConstant 和 L 工具类
+const WatchConstant = {
+    nameList: ['watch1', 'watch2', 'watch3'] // 假设的名称列表
+};
+
+
+// 电量对应图标
+function kwh2Icon({ kwh }) {
+    if (kwh <= 0) {
+        icon = "./../../img/dl0.png";
+    } else if (kwh > 0 && kwh <= 1) {
+        icon = "./../../img/dl1.png";
+    } else if (kwh > 1 && kwh <= 4) {
+        icon = "./../../img/dl2.png";
+    } else if (kwh > 4 && kwh <= 7) {
+        icon = "./../../img/dl3.png";
+    } else if (kwh > 7 && kwh <= 9) {
+        icon = "./../../img/dl4.png";
+    } else if (kwh === 10) {
+        icon = "./../../img/dl5.png";
+    }
+    return icon;
+}
+
+// 电量文案
+function kwh2Text({ kwh }) {
+    L.w(`电量状态====${kwh}`);
+    let k;
+    if (kwh < 0) {
+        k = 0;
+    } else if (kwh > 9) {
+        k = 9;
+        return kwh === 0xFF ? "" : "充电中";
+    } else {
+        k = kwh;
+    }
+    return `${k + 1}0%`;
+}

+ 367 - 0
devices/ble_manager.js

@@ -0,0 +1,367 @@
+// 引入必要的微信小程序 API
+// const wx = require('wx');
+
+class bleManager {
+    constructor() {
+        this.hasPermission = false;
+        this.isAvailable = false;
+        // this.devices = [];
+        this.connectedDeviceId = null;
+        // this.services = [];
+        this.serviceId = null;
+        this.deviceId = null;
+        this.characteristicId = null;
+    }
+
+    // 检查蓝牙权限
+    async checkBluetoothPermission() {
+        return new Promise((resolve, reject) => {
+            // 获取蓝牙权限
+            const _this = getApp();
+            wx.getSetting({
+                success(res) {
+                    if (res.authSetting["scope.bluetooth"]) {
+                        _this.globalData.scopeBluetooth = true;
+                        resolve(true);
+
+                    } else if (res.authSetting["scope.bluetooth"] === undefined) {
+                        _this.globalData.scopeBluetooth = false;
+                        wx.authorize({
+                            scope: "scope.bluetooth",
+                            complete() {
+                                _this.getBluetoothStatus();
+                            }
+                        });
+                        resolve(false);
+
+                    } else {
+                        _this.globalData.scopeBluetooth = false;
+
+                        wx.showModal({
+                            title: '请打开系统蓝牙进行配网',
+                            content: '如已打开蓝牙仍然弹框,请尝试重启小程序',
+                            success(res) {
+                                if (res.confirm) {
+                                    console.log('用户点击确定')
+                                    wx.openSetting({
+                                        complete() {
+                                            // _this.getBluetoothStatus();
+                                        }
+                                    })
+                                } else if (res.cancel) {
+                                    console.log('用户点击取消')
+                                }
+                            }
+                        })
+                        resolve(false);
+                    };
+                }
+            })
+        })
+
+
+
+    }
+
+    // 初始化蓝牙适配器
+    initBluetoothAdapter() {
+        return new Promise((resolve, reject) => {
+            wx.openBluetoothAdapter({
+                success: (res) => {
+                    this.isAvailable = true;
+                    resolve(true);
+                },
+                fail: (err) => {
+                    this.isAvailable = false;
+                    reject(false);
+                }
+            });
+        });
+    }
+
+    // 获取已连接的蓝牙设备
+    getConnectedDevices() {
+        return new Promise((resolve, reject) => {
+            wx.getConnectedBluetoothDevices({
+                services: ["ffc0", "ab00", "ffe5"],
+                // services: ["0000ab00-0000-1000-8000-00805f9b34fb"
+                //     , "0000ffc0-0000-1000-8000-00805f9b34fb"
+                // ],
+                success: (res) => {
+                    // const connectedDevices = res.devices.map(device => ({
+                    //     deviceId: device.deviceId,
+                    //     name: device.name || device.localName
+                    // }));
+                    console.log('已连接的蓝牙设备:', res.devices);
+                    resolve(res.devices);
+                },
+                fail: (err) => {
+                    console.error('获取已连接的蓝牙设备失败:', err);
+                    reject(new Error('获取已连接的蓝牙设备失败'));
+                }
+            });
+        });
+    }
+
+    // 断开与指定设备的连接
+    disconnect(deviceId) {
+        return new Promise((resolve, reject) => {
+            wx.closeBLEConnection({
+                deviceId: deviceId,
+                success: (res) => {
+                    this.connectedDeviceId = null;
+                    console.log('断开连接成功:', res);
+                    resolve(res);
+                },
+                fail: (err) => {
+                    console.error('断开连接失败:', err);
+                    reject(new Error('断开连接失败'));
+                }
+            });
+        });
+    }
+
+    // 发送数据到指定设备
+    sendData(data) {
+        return new Promise((resolve, reject) => {
+            const buffer = new ArrayBuffer(data.length);
+            const view = new Uint8Array(buffer);
+            for (let i = 0; i < data.length; i++) {
+                view[i] = data.charCodeAt(i);
+            }
+
+            wx.writeBLECharacteristicValue({
+                deviceId: this.deviceId,
+                serviceId: this.serviceId,
+                characteristicId: this.characteristicId,
+                value: buffer,
+                success: (res) => {
+                    console.log('数据发送成功:', res);
+                    resolve(res);
+                },
+                fail: (err) => {
+                    console.error('数据发送失败:', err);
+                    reject(new Error('数据发送失败'));
+                }
+            });
+        });
+    }
+
+    getSetting() {
+        const _this = this;
+        wx.getSetting({
+            success(res) {
+                if (res.authSetting["scope.userFuzzyLocation"]) {
+                    // 成功
+                    _this.getBluetoothStatus();
+
+                } else if (res.authSetting["scope.userFuzzyLocation"] === undefined) {
+
+                    wx.authorize({
+                        scope: "scope.userFuzzyLocation",
+                        success() {
+                            _this.getSetting();
+                        }
+                    });
+                } else {
+
+                    wx.showModal({
+                        title: '请打开系统位置获取',
+                        success(res) {
+                            if (res.confirm) {
+                                console.log('用户点击确定')
+                                wx.openSetting({
+                                    complete() {
+                                        // _this.getSetting();
+                                    }
+                                })
+                            } else if (res.cancel) {
+                                console.log('用户点击取消');
+                            }
+                        }
+                    })
+                }
+            }
+        })
+    }
+    // 开始搜索蓝牙设备
+    startScan(success, fail) {
+        wx.startBluetoothDevicesDiscovery({
+            services: ["ffc0", "ab00", "ffe5"],
+            success: (res) => {
+                // this.onBluetoothDeviceFound();
+                console.log('蓝牙设备搜索已启动:', res);
+                if (success) {
+                    success(res);
+                }
+
+                // resolve(res);
+            },
+            fail: (err) => {
+                if (fail) {
+                    fail(err);
+                }
+                console.log('启动蓝牙设备搜索失败', err ?? "err is null");
+
+                // reject(new Error('启动蓝牙设备搜索失败', err ?? "err is null"));
+            }
+        });
+        // return new Promise((resolve, reject) => {
+        //     wx.startBluetoothDevicesDiscovery({
+        //         services: ["ffc0", "ab00", "ffe5"],
+        //         success: (res) => {
+        //             // this.onBluetoothDeviceFound();
+        //             console.log('蓝牙设备搜索已启动:', res);
+        //             resolve(res);
+        //         },
+        //         fail: (err) => {
+        //             reject(new Error('启动蓝牙设备搜索失败', err ?? "err is null"));
+        //         }
+        //     });
+        // });
+    }
+
+    // 监听发现新设备的事件
+    onBluetoothDeviceFound(callback) {
+        wx.onBluetoothDeviceFound((res) => {
+            const devices = res.devices.map(device => ({
+                deviceId: device.deviceId,
+                name: device.name || device.localName
+            }));
+            // this.devices.push(...devices);
+            console.log('发现设备:', this.devices);
+            if (callback) {
+                callback(devices);
+            }
+        });
+    }
+
+    // 连接到指定设备
+    connectToDevice(deviceId) {
+        return new Promise((resolve, reject) => {
+            wx.createBLEConnection({
+                deviceId: deviceId,
+                success: (res) => {
+                    this.connectedDeviceId = deviceId;
+                    console.log('连接成功:', res);
+                    resolve(res);
+                },
+                fail: (err) => {
+                    console.error('连接失败:', err);
+                    reject(new Error('连接失败'));
+                }
+            });
+        });
+    }
+
+    // 停止搜索
+    stopScan() {
+        return new Promise((resolve, reject) => {
+            wx.stopBluetoothDevicesDiscovery({
+                success: (res) => {
+                    console.log('停止搜索成功:', res);
+                    resolve(res);
+                },
+                fail: (err) => {
+                    console.error('停止搜索失败:', err);
+                    reject(new Error('停止搜索失败'));
+                }
+            });
+        });
+    }
+
+    // 发现服务
+    discoverServices(deviceId) {
+        return new Promise((resolve, reject) => {
+            wx.getBLEDeviceServices({
+                deviceId: deviceId,
+                success: (res) => {
+                    this.services = res.services;
+                    console.log('发现服务:', this.services);
+                    resolve(res.services);
+                },
+                fail: (err) => {
+                    console.error('发现服务失败:', err);
+                    reject(new Error('发现服务失败'));
+                }
+            });
+        });
+    }
+
+    // 发现特征值
+    discoverCharacteristics(deviceId, serviceId) {
+        return new Promise((resolve, reject) => {
+            wx.getBLEDeviceCharacteristics({
+                deviceId: deviceId,
+                serviceId: serviceId,
+                success: (res) => {
+                    // this.characteristics[serviceId] = res.characteristics;
+                    this.characteristicId = res.characteristics;
+                    console.log('发现特征值:', this.characteristicId);
+                    resolve(res.characteristics);
+                },
+                fail: (err) => {
+                    console.error('发现特征值失败:', err);
+                    reject(new Error('发现特征值失败'));
+                }
+            });
+        });
+    }
+
+    // 读取特征值
+    readCharacteristicValue(deviceId, serviceId, characteristicId) {
+        return new Promise((resolve, reject) => {
+            wx.readBLECharacteristicValue({
+                deviceId: deviceId,
+                serviceId: serviceId,
+                characteristicId: characteristicId,
+                success: (res) => {
+                    console.log('读取特征值成功:', res);
+                    resolve(res);
+                },
+                fail: (err) => {
+                    console.error('读取特征值失败:', err);
+                    reject(new Error('读取特征值失败'));
+                }
+            });
+        });
+    }
+
+    // 监听特征值变化
+    notifyCharacteristicValueChange(deviceId, serviceId, characteristicId, state, callback) {
+        // return new Promise((resolve, reject) => {
+        wx.notifyBLECharacteristicValueChange({
+            deviceId: deviceId,
+            serviceId: serviceId,
+            characteristicId: characteristicId,
+            state: state, // true 表示开启通知,false 表示关闭通知
+            success: (res) => {
+                console.log('通知特征值变化成功:', res);
+                // resolve(res);
+                if (callback) {
+                    callback(res)
+                }
+            },
+            fail: (err) => {
+                console.error('通知特征值变化失败:', err);
+                // reject(new Error('通知特征值变化失败'));
+            }
+        });
+        // });
+    }
+
+    // 搜索蓝牙服务
+    searchServices(deviceId) {
+        return this.discoverServices(deviceId);
+    }
+
+    // 搜索蓝牙特征值
+    searchCharacteristics(deviceId, serviceId) {
+        return this.discoverCharacteristics(deviceId, serviceId);
+    }
+}
+
+const ble = new bleManager();
+
+// 导出 bleManager 类
+module.exports = ble;

+ 406 - 0
devices/bluetooth/bt_cmd.js

@@ -0,0 +1,406 @@
+// 设备命令
+// TLV格式(16进制)
+// 二进制协议使用 TLV (Type-Length-Value)编码格式。
+// - Type:固定长度, 一字节。
+// - Length:可变长度。 0 或 1 个字节。
+// - Value:二进制数据。 长度由 Length 字段决定。 数据本身可以上数字或者字符串。
+
+const EnumOpen = {
+    close: 0,
+    open: 1
+};
+
+const EnumConnectType = {
+    bt: 0,
+    ble: 1,
+    upnp: 2,
+    mqtt: 3
+}
+
+class BtCmd {
+    // BtCmd._();
+    // constructor() { }
+
+    // 获取版本号
+    static get queryVersion() {
+        return this._build(CmdBase.queryVersion, [0x0]);
+    }
+
+    // 查询电量  电量值 0-9,0对应10%,9对应100%,10充电
+    static get queryKwh() {
+        return this._build(CmdKwh.queryKwh, [0x0]);
+    }
+
+    // 查询耳机电量
+    static get queryKwhEarPhone() {
+        return this._build(CmdEarPhone.queryEarPhoneKwh, [0x0]);
+    }
+
+    // 查询EQ音效
+    static get queryEQ() {
+        return this._build(CmdEarPhone.queryEQ, [0x0]);
+    }
+
+    // 设置EQ音效
+    static setEQ(list) {
+        return this._build(CmdEarPhone.setEQ, list);
+    }
+
+    // B5耳机-低电耗模式
+    static get queryLowPower() {
+        return this._build(CmdEarPhone.queryLowPower, [0x0]);
+    }
+
+    static setLowPowerMode(isOpen) {
+        return this._build(CmdEarPhone.setLowPowerMode, [isOpen]);
+    }
+
+    // B5耳机-自定义操作手势
+    static get queryCtrlStatus() {
+        return this._build(CmdEarPhone.queryCtrlStatus, [0x0]);
+    }
+
+    static setCtrlStatus(singleClick, doubleClick, longClick) {
+        return this._build(CmdEarPhone.setCtrlStatus, [singleClick, doubleClick, longClick]);
+    }
+
+    // 查询低延时模式
+    static get queryLowDelayMode() {
+        return this._build(CmdEarPhone.queryLowDelayMode, [0x0]);
+    }
+
+    // 低延时模式
+    static setLowDelayMode(open, mode) {
+        return this._build(CmdEarPhone.setLowDelayMode, [open, mode]);
+    }
+
+    // 低电量提示音
+    static get queryLowKwhWarningTone() {
+        return this._build(CmdKwh.queryLowKwhWarningTone, [0x00]);
+    }
+
+    static setLowKwhWarningTone(isNotify) {
+        return this._build(CmdKwh.setLowKwhWarningTone, isNotify ? [0x01] : [0x00]);
+    }
+
+    // 蓝牙自动播放
+    static setAutoPlay(switchStatus) {
+        return this._build(CmdBase.setAutoPlay, [switchStatus]);
+    }
+
+    static get getAutoPlay() {
+        return this._build(CmdBase.getAutoPlay, [0x00]);
+    }
+
+    // 查询音量
+    static get queryVolume() {
+        return this._build(CmdBase.queryVolume, [0x00]);
+    }
+
+    static setVolume(volume) {
+        return this._build(CmdBase.setVolume, [volume]);
+    }
+
+    // 查询闹钟
+    static get queryAlarm() {
+        return this._build(CmdRtc.queryAlarm, [0x00]);
+    }
+
+    // 设置闹钟
+    static setAlarm(switchStatus, weekCycle, hour, minutes, channel = 1) {
+        const weeks = CmdWeek.week2Cmd(weekCycle);
+        return this._build(CmdRtc.setAlarm, [switchStatus, weeks, hour, minutes, channel]);
+    }
+
+    // 查询休眠
+    static get querySleep() {
+        return this._build(CmdRtc.querySleep, [0x00]);
+    }
+
+    static get querySleepAfterPlayPause() {
+        return this._build(CmdRtc.querySleepAfterPlayPause, [0x00]);
+    }
+
+    static setSleepAfterPlayPause(time) {
+        return this._build(CmdRtc.setSleepAfterPlayPause, [time]);
+    }
+
+    // 设置休眠
+    static setSleep(switchStatus, hour, minutes) {
+        return this._build(CmdRtc.setSleep, [switchStatus, hour, minutes]);
+    }
+
+    // 设置时间
+    static get setTime() {
+        const y = TimeUtil.getYear();
+        const year = Math.floor(y / 100);
+        const _year = y % 100;
+        const month = TimeUtil.getMonth();
+        const day = TimeUtil.getDay();
+        const hour = TimeUtil.getHour();
+        const minutes = TimeUtil.getMinute();
+        const seconds = TimeUtil.getSecond();
+
+        console.log(`setTime========${year},${_year},${month},${day},${hour},${minutes},${seconds}`);
+        return this._build(CmdRtc.setTime, [year, _year, month, day, hour, minutes, seconds]);
+    }
+
+    // 查询时间
+    static get queryTime() {
+        return this._build(CmdRtc.queryTime, [0x00]);
+    }
+
+    // 设置RGB
+    static setRGB(r, g, b) {
+        return this._build(CmdRgb.set, [r, g, b]);
+    }
+
+    // 查询RGB
+    static get queryRGB() {
+        return this._build(CmdRgb.query, [0x0]);
+    }
+
+    // 校验设备
+    static get checkDevice() {
+        return this._build(CmdBase.checkDevice, StringUtil.string2ListInt(CmdBase.secretKey));
+    }
+
+    // 杰理语音开始回应
+    static get jlStartVoiceResponse() {
+        return this._build(CmdVoice.jlStartVoiceResponse);
+    }
+
+    // 杰理语音结束回应
+    static get jlStopVoiceResponse() {
+        return this._build(CmdVoice.jlStopVoiceResponse);
+    }
+
+    // 领芯语音
+    static get lxStartVoiceResponse() {
+        return this._build(CmdVoice.lxStartVoiceResponse);
+    }
+
+    // 杰理语音结束回应
+    static get lxStopVoiceResponse() {
+        return this._build(CmdVoice.lxStopVoiceResponse);
+    }
+
+    // b1开始语音回调
+    static get b1StartVoiceResponse() {
+        const data = [0x04, 0x80, 0x01, 0x00, 0x02];
+        return new Uint8Array(data);
+    }
+
+    // b1结束语音回调
+    static get b1StopVoiceResponse() {
+        const data = [0x05, 0x80, 0x01, 0x00, 0x02];
+        return new Uint8Array(data);
+    }
+
+    // 固定头部
+    static get _header() {
+        return [0x54, 0x44, 0x44, 0x48];
+    }
+
+    // 版本号
+    static get _version() {
+        return 0x01;
+    }
+
+    // 生成命令(固定头部+版本号+命令总长度+命令类型+)
+    static _build(cmdType, otherCmd = [], isWriteChildCmdLength = true) {
+        const cmd = [];
+
+        // 固定头部
+        cmd.push(...this._header);
+
+        // 版本号
+        cmd.push(this._version);
+
+        // 命令类型
+        cmd.push(cmdType);
+
+        if (isWriteChildCmdLength) {
+            // 子命令长度
+            const childLength = this._int2Hex(otherCmd.length);
+            cmd.push(childLength);
+        }
+
+        // 其他命令
+        if (otherCmd.length > 0) {
+            for (const element of otherCmd) {
+                cmd.push(this._int2Hex(element));
+            }
+        }
+
+        // 命令长度(位置在版本号之后)
+        const length = cmd.length + 1;
+        const l = this._int2Hex(length);
+        cmd.splice(5, 0, l);
+        return new Uint8Array(cmd);
+    }
+
+    // 打印cmd
+    static printTLV(cmd, isSend = false) {
+        let result = "";
+        const key = isSend ? "发送" : "接收";
+        if (isSend) {
+            // console.log(`${key}原始-${cmd.toString()}`);
+        }
+
+        for (let i = 0; i < cmd.length; i++) {
+            const cmdStr = `${this._int2HexString(cmd[i])}`.padStart(2, "0");
+            result += cmdStr;
+        }
+        if (isSend) {
+            mqttAddDebugCmd(`发送蓝牙指令:${cmd} \n ${result}`);
+        }
+        console.log(`${key}解析-[${result}]`);
+    }
+
+    static _int2Hex(value) {
+        return value & 0xFF;
+    }
+
+    static _int2HexString(value) {
+        return value.toString(16).toUpperCase();
+    }
+}
+
+// 设备命令类型
+const CmdBase = {
+    secretKey: "smart_A2F",
+    queryVersion: 0x30,
+    checkDevice: 0x28,
+    checkDeviceSuccess: 0x27,
+    setAutoPlay: 0x24,
+    getAutoPlay: 0x23,
+    queryChannelAngle: 0x38,
+    switchLed: 0x39,
+    queryVolume: 0x32,
+    setVolume: 0x33,
+    newFlag: "MW-Mate X\\(4G_WIFI\\)|MW-2AX\\(WIFI-N\\)|MW-SR1\\(4G_WIFI\\)|MW-SR1\\(4G_WIFI_MEIZU01\\)",
+    // volumeMax: (new RegExp(`^(${CmdBase.newFlag})$`, 'i').test(DeviceManager.instance.clientType || "")) ? 0x20 : 0xF,
+
+    parseVersion(version) {
+        let ver;
+        if (version <= 0) {
+            ver = "1.0.0";
+        } else {
+            const split = version.toString().split("");
+            if (split.length === 1) {
+                split.unshift("0", "0");
+            } else if (split.length === 2) {
+                split.unshift("0");
+            }
+            ver = split.join(".");
+        }
+        console.log(`parseVersion======${version},,,,,,,${ver.toString()}`);
+        return ver;
+    }
+};
+
+// 耳机相关指令
+const CmdEarPhone = {
+    queryLowDelayMode: 0xb2,
+    setLowDelayMode: 0xb3,
+    queryEQ: 0xb4,
+    setEQ: 0xb5,
+    queryEarPhoneKwh: 0xb1,
+    queryCtrlStatus: 0xB6,
+    setCtrlStatus: 0xB7,
+    queryLowPower: 0xB8,
+    setLowPowerMode: 0xB9
+};
+
+// 时间相关
+const CmdRtc = {
+    setTime: 0x50,
+    queryTime: 0x51,
+    setAlarm: 0x52,
+    queryAlarm: 0x54,
+    setSleep: 0x56,
+    querySleep: 0x57,
+    setSleepAfterPlayPause: 0x5a,
+    querySleepAfterPlayPause: 0x5b
+};
+
+// 设置设备颜色
+const CmdRgb = {
+    query: 0x80,
+    set: 0x81
+};
+
+// 电量相关命令
+const CmdKwh = {
+    queryKwh: 0x22,
+    setLowKwhWarningTone: 0x26,
+    queryLowKwhWarningTone: 0x25
+};
+
+// 按键
+const CmdKey = {
+    previousSong: 0x0a,
+    nextSong: 0x0b,
+    previousChannel: 0x0c,
+    nextChannel: 0x0d,
+    type: 0x21,
+    pressShort: 0x02,
+    pressLongUp: 0x04,
+    pressLong: 0x08,
+    pressDouble: 0x09,
+    next: 0x01,
+    previous: 0x02,
+    changeProgram: 0x03
+};
+
+// 语音
+const CmdVoice = {
+    startVoice: 0x01,
+    jlStartVoice: 0x66,
+    jlStopVoice: 0x68,
+    jlStartVoiceResponse: 0x67,
+    jlStopVoiceResponse: 0x69,
+    jlReceiveVoiceData: 0x70,
+    lxStartVoice: 0x71,
+    lxStopVoice: 0x72,
+    lxStartVoiceResponse: 0x40,
+    lxStopVoiceResponse: 0x41,
+    sjStartVoice: 0x23,
+    sjStopVoice: 0x24
+};
+
+// 一周
+const CmdWeek = {
+    sunday: 0x01,
+    monday: 0x02,
+    tuesday: 0x04,
+    wednesday: 0x08,
+    thursday: 0x10,
+    friday: 0x20,
+    saturday: 0x40,
+    weeks: [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40],
+
+    week2Cmd(weekCycle) {
+        let result = 0;
+        for (let i = 0; i < weekCycle.length; i++) {
+            const item = weekCycle[i];
+            if (item === EnumOpen.open) {
+                result += this.weeks[i];
+            }
+        }
+        return result;
+    },
+
+    cmd2Week(cmd) {
+        const w = [0, 0, 0, 0, 0, 0, 0];
+        for (let i = this.weeks.length - 1; i >= 0; i--) {
+            const week = this.weeks[i];
+            if (week <= cmd) {
+                w[i] = EnumOpen.open;
+                cmd -= week;
+            }
+        }
+        return w;
+    }
+};

+ 417 - 0
devices/bluetooth/bt_parse.js

@@ -0,0 +1,417 @@
+import { BtCmd } from './bt_cmd.js';
+
+class BtParse {
+    // 型号名称
+    // static mateX2 = "MW-Mate X(4G_WIFI)";
+    // static matePVX = "猫王音响·Mate_PVX";
+    // static matePVXL = "猫王音响·Mate_PVXL";
+
+    // 防止收到另外的消息
+    static isReceiveCmd = false;
+
+    // 如果10ms内获取两个一模一样的就删除一个
+    static lastCmd = null;
+    static lastTime = null;
+
+    // 解析命令
+    static async parseTLV(cmd) {
+        BtCmd.printTLV(cmd, false);
+        // todo 在线
+        // if (!PlayDevice.isOnline) {
+        //     return;
+        // }
+        if (!BtParse.isReceiveCmd) {
+            return;
+        }
+
+        if (cmd.length > 5) {
+            // 指令类别
+            const type = cmd[6];
+
+            if (cmd.length < 9) {
+                // 杰里、领芯的语音控制指令
+                BtParse._voiceCmd(cmd);
+            } else {
+                // cmd[7]为命令长度
+                // 命令类型
+                const value = cmd[8];
+
+                // 第二部分走杰理
+                if (type === CmdVoice.jlReceiveVoiceData) {
+                    BtParse._receiveRecordData(cmd);
+                }
+
+                // 查询版本号后校验设备
+                else if (type === CmdBase.queryVersion) {
+                    const version = CmdBase.parseVersion(value);
+                    EventManager.fire(CmdEvent.version(version));
+                    EventManager.fire(CmdEvent.getDeviceInfo());
+                } else if (BtParse._getDataHeader(cmd)) {
+                    // 控制指令
+                    BtParse._controlCmd(cmd);
+                } else {
+                    const manufacturer = DeviceManager.instance.device?.manufacturer ?? "";
+
+                    // 山景的语音判断
+                    if (manufacturer === EnumSupplier.shanJing) {
+                        const sjCmd = cmd[4];
+                        if (sjCmd === CmdVoice.sjStartVoice) {
+                            BtParse._startRecord(EnumSupplier.shanJing);
+                        } else if (sjCmd === CmdVoice.sjStopVoice) {
+                            BtParse._stopRecord(EnumSupplier.shanJing);
+                        }
+
+                        // 山景的都要走接收数据
+                        BtParse._receiveRecordData(cmd);
+                    } else {
+                        // b1耳机、领芯、杰里p2第二部分语音数据直接走这里
+                        BtParse._receiveRecordData(cmd);
+                    }
+                }
+            }
+        }
+    }
+
+    static _send(cmdEvent) {
+        // todo
+        // EventManager.fire(CmdEvent[cmdEvent]);
+    }
+
+    static _getDataHeader(cmd) {
+        if (cmd.length < 4) {
+            return false;
+        }
+        const header = [0x54, 0x44, 0x44, 0x48];
+        return cmd[0] === header[0] && cmd[1] === header[1] && cmd[2] === header[2] && cmd[3] === header[3];
+    }
+
+    // todo 暂时不要
+    // static _voiceCmd(cmd) {
+    //     const type = cmd[6];
+
+    //     switch (type) {
+    //         // 杰理语音开始
+    //         case CmdVoice.jlStartVoice:
+    //             BtParse._startRecord(EnumSupplier.jieLi);
+    //             break;
+
+    //         // 杰理结束
+    //         case CmdVoice.jlStopVoice:
+    //             BtParse._stopRecord(EnumSupplier.jieLi);
+    //             break;
+
+    //         // 发给插件
+    //         case CmdVoice.jlReceiveVoiceData:
+    //             console.log("发送杰里的p1语音:没有进来过,不走这里的,直接判断0x70 && jieLi");
+    //             BtParse._receiveRecordData(cmd);
+    //             break;
+
+    //         // 领心语音开始
+    //         case CmdVoice.lxStartVoice:
+    //             BtParse._startRecord(EnumSupplier.lingXin);
+    //             break;
+
+    //         // 领心语音结束
+    //         case CmdVoice.lxStopVoice:
+    //             BtParse._stopRecord(EnumSupplier.lingXin);
+    //             break;
+
+    //         default:
+    //             console.log("发送语音:默认发送,不走这里", cmd);
+    //             break;
+    //     }
+    // }
+
+    static async _startRecord(supplier) {
+        console.log("StartVoice======语音开始", supplier);
+    }
+
+    static async _stopRecord(supplier) {
+        console.log("stopRecord======语音结束", supplier);
+    }
+
+    // 杰理接收设备第二部分 发送给插件
+    static _receiveRecordData(cmd) {
+        // todo 暂时不要
+        // RecordVoiceManager.sendDataToPlugin(cmd);
+    }
+
+    static async _controlCmd(cmd) {
+        const type = cmd[6];
+        const value = cmd[8];
+        // mqttAddDebugCmd("收到蓝牙指令:" + cmd);
+
+        switch (type) {
+            // 校验设备
+            case CmdBase.checkDeviceSuccess:
+                // 设置100ms间隔,同时发会丢失指令
+                // DeviceManager.instance.setLowKwhWarningTone();
+                // DeviceManager.instance.queryLowKwhWarningTone();
+                // DeviceManager.instance.getAutoPlay();
+                // DeviceManager.instance.setTime();
+                // DeviceManager.instance.getDeviceInfo();
+                break;
+
+            // 按键   短按、长按等
+            case CmdKey.type:
+                if (DeviceUtil.isIOS) {
+                    if (BtParse.lastCmd && JSON.stringify(BtParse.lastCmd) === JSON.stringify(cmd)) {
+                        if (BtParse.lastTime) {
+                            const curTime = TimeUtil.getCurrentMillis();
+                            if (curTime - BtParse.lastTime < 5) {
+                                return;
+                            }
+                        }
+                    }
+                    BtParse.lastCmd = cmd;
+                    BtParse.lastTime = TimeUtil.getCurrentMillis();
+                }
+                let t = 0;
+                if (cmd.length > 9) {
+                    t = cmd[9];
+                }
+
+                switch (t) {
+                    // 0x54 44 44 48 01 0a 21 02 01 02
+                    // 54, 44, 44, 48, 1, a, 21, 2, 1(value), 2(type)
+
+                    // 上一曲
+                    case CmdKey.previousSong:
+                        BtParse._send(EnumCmdEvent.previousSong);
+                        break;
+
+                    // 下一曲
+                    case CmdKey.nextSong:
+                        BtParse._send(EnumCmdEvent.nextSong);
+                        break;
+
+                    // 上一频道
+                    case CmdKey.previousChannel:
+                        BtParse._send(EnumCmdEvent.previousChannel);
+                        break;
+
+                    // 下一频道
+                    case CmdKey.nextChannel:
+                        BtParse._send(EnumCmdEvent.nextChannel);
+                        break;
+
+                    // 0x02 2
+                    case CmdKey.pressShort:
+                        // 1 下一个频道
+                        if (value === CmdKey.next) {
+                            BtParse._send(EnumCmdEvent.nextChannel);
+                        }
+                        // 2 上一个频道
+                        else if (value === CmdKey.previous) {
+                            BtParse._send(EnumCmdEvent.previousChannel);
+                        }
+
+                        // 3 单击下一个专辑 或者下一曲
+                        else if (value === CmdKey.changeProgram) {
+                            if (ProviderUtil.device.mDevice?.clientType === BtParse.mateX2) {
+                                BtParse._send(EnumCmdEvent.nextAlbum);
+                            } else {
+                                BtParse._send(EnumCmdEvent.nextSong);
+                            }
+                        }
+                        break;
+
+                    // 0x09 9
+                    case CmdKey.pressDouble:
+                        // 1 双击下一曲
+                        if (value === CmdKey.next) {
+                            BtParse._send(EnumCmdEvent.nextSong);
+                        }
+                        // 2 双击上一曲
+                        else if (value === CmdKey.previous) {
+                            BtParse._send(EnumCmdEvent.previousSong);
+                        }
+                        // 3 单击上一专辑或者上一曲
+                        else if (value === CmdKey.changeProgram) {
+                            if (ProviderUtil.device.mDevice?.clientType === BtParse.mateX2) {
+                                BtParse._send(EnumCmdEvent.previousAlbum);
+                            } else {
+                                BtParse._send(EnumCmdEvent.previousSong);
+                            }
+                        }
+                        break;
+
+                    // 0x08 8
+                    case CmdKey.pressLong:
+                        if (value === CmdKey.previous) {
+                            console.log("快退");
+                            BtParse._send(EnumCmdEvent.fastback);
+                        } else if (value === CmdKey.next) {
+                            console.log("快进");
+                            BtParse._send(EnumCmdEvent.speed);
+                        }
+                        break;
+
+                    // 0x04 4
+                    case CmdKey.pressLongUp:
+                        if (value === CmdKey.previous) {
+                            console.log("停止快退");
+                            BtParse._send(EnumCmdEvent.stopFastback);
+                        } else if (value === CmdKey.next) {
+                            console.log("停止快进");
+                            BtParse._send(EnumCmdEvent.stopSpeed);
+                        } else if (value === CmdKey.pressLongUp) {
+                            // [54, 44, 44, 48, 1, a, 21, 2, 4, 4]
+                            console.log("录音结束");
+                            BtParse._send(EnumCmdEvent.stopRecord);
+                        }
+                        break;
+                }
+                BtHelper.instance.changeChannelCallBack();
+                break;
+
+            // 音量 54 44 44 48 1 a 32 2 b 0
+            case CmdBase.queryVolume:
+                // 手机最大音量 需要获取
+                const phoneMax = await VolumeUtil.getMaxVolume();
+                const max = CmdBase.volumeMax;
+                const d = phoneMax / max;
+
+                const result = (d * value).toFixed(2);
+                const trueVolume = parseFloat(result);
+
+                console.log("trueVolume=====", max, "===", d, ",,,,", value, ",,,,", result, ",,,,", trueVolume);
+                if (trueVolume > phoneMax) {
+                    trueVolume = phoneMax;
+                }
+
+                // 仅用于记录设备音量,播控页面显示用
+                EventManager.fire(CmdEvent.volume({ volume: trueVolume }));
+                if (DeviceUtil.isAndroid) {
+                    const name = DeviceManager.instance.name;
+                    if (name === "猫王·霹雳唱机") {
+                        VolumeUtil.setVolume(trueVolume);
+                    }
+                }
+                break;
+
+            // 查询电量
+            case CmdKwh.queryKwh:
+                EventManager.fire(CmdEvent.battery({ kwh: value }));
+                break;
+
+            // 自动播放 54, 44, 44, 48, 1, 9, 24, 1, 1
+            case CmdBase.getAutoPlay:
+            case CmdBase.setAutoPlay:
+                setTimeout(() => {
+                    EventManager.fire(CmdEvent.playStatus({
+                        playStatus: value === EnumOpen.open ? EnumPlayStatus.play : EnumPlayStatus.pause,
+                    }));
+                }, 300);
+                break;
+
+            // 查询低电量设置
+            case CmdKwh.queryLowKwhWarningTone:
+                EventManager.fire(CmdEvent.queryLowKwn({ kwh: value }));
+                break;
+
+            // 耳机电量 index0 :左耳机, index1:左耳机 ,index2: 充电盒。value: 0-9代表10-100% 获取不到用-1
+            // [54, 44, 44, 48, 1, b, b1, 3, 8, 8, 2]
+            case CmdEarPhone.queryEarPhoneKwh:
+                const rightKwh = cmd[9];
+                const boxKwh = cmd[10];
+
+                let kwh = 0;
+                if (value > 0 && rightKwh > 0) {
+                    kwh = Math.min(value, rightKwh);
+                } else {
+                    kwh = Math.max(value, rightKwh);
+                }
+
+                EventManager.fire(CmdEvent.batteryEarphone({
+                    kwhLeft: value,
+                    kwhBox: boxKwh,
+                    kwhRight: rightKwh,
+                    kwh: kwh,
+                }));
+                break;
+
+            // EQ  [84, 68, 68, 72, 1, 18, 180, 10, 0, 0, 240, 237, 236, 0, 236, 0, 0, 0]
+            // B5耳机获取EQ
+            case CmdEarPhone.queryEQ:
+                if (cmd.length > 15) {
+                    const result = cmd.slice(8, cmd.length);
+                    const list = result.map(element => parseInt(element, 16));
+                    console.log("eq==============", list);
+                    EventManager.fire(CmdEvent.eqs({ eqs: list }));
+                }
+                break;
+
+            // 低延迟  [54, 44, 44, 48, 1, a, b2, 2, 0, 2]
+            case CmdEarPhone.queryLowDelayMode:
+                const lowDelayMode = cmd[9];
+                EventManager.fire(CmdEvent.lowDelayMode({ lowDelayMode, lowDelayModeOpen: value }));
+                break;
+
+            // 耳机低电量
+            case CmdEarPhone.queryLowPower:
+                const lowPowerOpen = cmd[8];
+                EventManager.fire(CmdEvent.lowPowerOpen({ lowPowerOpen }));
+                break;
+
+            // 查询唤醒闹钟
+            case CmdRtc.queryAlarm:
+                const wakeSwitch = cmd[8];
+                if (cmd.length > 9) {
+                    const week = cmd[9];
+                    const hour = cmd[10];
+                    const minutes = cmd[11];
+                    const wakeCycle = CmdWeek.cmd2Week(week);
+                    console.log("queryAlarm=====", wakeSwitch, "=", wakeCycle);
+                    EventManager.fire(CmdEvent.wake({
+                        wakeSwitch,
+                        wakeCycle,
+                        wakeHour: hour,
+                        wakeMinutes: minutes,
+                    }));
+                }
+                break;
+
+            // 查询自动休眠状态
+            case CmdRtc.querySleepAfterPlayPause:
+            case CmdRtc.setSleepAfterPlayPause:
+                EventManager.fire(CmdEvent.pauseSleep({ pauseSleep: value }));
+                break;
+
+            // 查询休眠
+            case CmdRtc.querySleep:
+                const sleepSwitch = cmd[8];
+                const hour = cmd[9];
+                const minutes = cmd[10];
+                const leftHour = cmd[11];
+                const leftMinutes = cmd[12];
+                EventManager.fire(CmdEvent.sleep({
+                    sleepSwitch,
+                    sleepHour: hour,
+                    sleepMinutes: minutes,
+                }));
+                break;
+
+            // 开始录音
+            case CmdVoice.startVoice:
+                console.log("没有意义的开始录音");
+                // _send(EnumCmdEvent.startRecord);
+                break;
+
+            // 查询 耳机手势
+            case CmdEarPhone.queryCtrlStatus:
+                const singleC = cmd[8];
+                const doubleC = cmd[9];
+                const longC = cmd[10];
+                EventManager.fire(CmdEvent.ctrlStatus({ ctrlStatus: [singleC, doubleC, longC] }));
+                break;
+
+            default:
+                console.log("发送语音:默认发送2", cmd);
+                // _receiveRecordData(cmd);
+                break;
+        }
+    }
+}
+
+export { BtParse };

+ 374 - 0
devices/bt_helper.js

@@ -0,0 +1,374 @@
+// 引入必要的模块
+// const DeviceUtil = require('./DeviceUtil');
+// const BtAndroidHelper = require('./BtAndroidHelper');
+// const BtIOSHelper = require('./BtIOSHelper');
+// const QueueManager = require('./QueueManager');
+// const DeviceManager = require('./DeviceManager');
+const BtCmd = require('./../devices/bluetooth/bt_cmd');
+// const VolumeUtil = require('./VolumeUtil');
+// const EventManager = require('./EventManager');
+const CmdBase = require('./../devices/bluetooth/bt_cmd');
+const bleManager = require('./ble_manager');
+// const EnumConnectStatus = require('./EnumConnectStatus');
+// const EnumOpen = require('./EnumOpen');
+// const EnumSupplier = require('./EnumSupplier');
+// const EnumLowDelayModeOpen = require('./EnumLowDelayModeOpen');
+// const EnumLowDelayMode = require('./EnumLowDelayMode');
+// const EnumPlayStatus = require('./EnumPlayStatus');
+
+// 或者使用对象字面量
+const EnumSupplier = {
+    shanJing: "ShanJing",
+    jieLi: "JieLi",
+    lingXin: "LingXin",
+    qiXinWei: "QiXinWei"
+};
+
+const EnumPlayStatus = {
+    none: 'none',
+    play: 'play',
+    pause: 'pause',
+    stop: 'stop',
+    completed: 'completed',
+    error: 'error',
+    buffering: 'buffering', // APP自己定义的特殊处理中间状态
+};
+
+class BtHelper {
+    // static get instance() {
+    //     return this._instance;
+    // }
+
+    // static _instance = new BtHelper();
+
+    static _isConnecting = false;
+    static isDisConnectByOTA = false;
+
+    // _helper;
+
+    constructor() {
+        // if (DeviceUtil.isAndroid) {
+        //     this._helper = BtAndroidHelper.instance;
+        // } else {
+        //     this._helper = BtIOSHelper.instance;
+        // }
+
+        // QueueManager.instance.listenTask((task) => {
+        //     if (!DeviceManager.instance.isWatch) {
+        //         this._helper.send({ cmd: task });
+        //     }
+        // });
+    }
+
+
+    resetConnectState(connect) {
+        // BtHelper._isConnecting = connect;
+    }
+
+    async search(mqttFilterList) {
+        let res = await bleManager.startScan()
+        console.log(res);
+        // return await this._helper.search();
+    }
+
+    async findDevices(callback) {
+        bleManager.onBluetoothDeviceFound(callback)
+        // return await this._helper.search();
+    }
+
+    async stopSearch() {
+        bleManager.stopScan();
+    }
+
+    async connect(data, onChanged, isClick = false) {
+        // await this._helper.connect({ data, onChanged, isClick });
+        try {
+            await bleManager.connectToDevice(data.deviceId);
+            console.log('连接成功');
+            // this.setData({ connectedDeviceId: deviceId });
+            const services = await bleManager.searchServices(deviceId);
+            // this.setData({ services });
+            for (const service of services) {
+                const characteristics = await bleManager.searchCharacteristics(deviceId, service.uuid);
+                bleManager.readBLECharacteristicValue({
+                    deviceId: data.deviceId,
+                    serviceId: services.serviceId,
+                    characteristicId: characteristics,
+                })
+                // this.setData({ characteristics: { ...this.data.characteristics, [service.uuid]: characteristics } });
+            }
+        } catch (error) {
+            console.error(error);
+        }
+    }
+
+    async disconnect(data) {
+        // await this._helper.disconnect();
+        bleManager.disconnect(data.deviceId)
+    }
+
+    async dispose() {
+        // await this._helper.dispose();
+    }
+
+    async send(cmd, type) {
+        // bleManager.sendData()
+        // QueueManager.instance.addTask({ task: cmd });
+    }
+    // onLoad: function () {
+    //     bleManager = new bleManager();
+    //     this.initBluetooth();
+    //   }
+
+    async initBluetooth() {
+        try {
+            let res = await bleManager.initBluetoothAdapter();
+            console.log(res)
+
+
+            let per = await bleManager.checkBluetoothPermission();
+            console.log(per)
+        } catch (error) {
+            console.error(error);
+        }
+    }
+
+    async getConnectedDevices() {
+        try {
+            const connectedDevices = await bleManager.getConnectedDevices();
+            return connectedDevices
+        } catch (error) {
+            console.error(error);
+        }
+    }
+
+
+    // disconnectFromDevice: async function(deviceId) {
+    //     try {
+    //         await bleManager.disconnectFromDevice(deviceId);
+    //         console.log('断开连接成功');
+    //         this.setData({ connectedDeviceId: null });
+    //     } catch (error) {
+    //         console.error(error);
+    //     }
+    // },
+
+    // sendData: async function(deviceId, serviceId, characteristicId, data) {
+    //     try {
+    //         await bleManager.sendData(deviceId, serviceId, characteristicId, data);
+    //         console.log('数据发送成功');
+    //     } catch (error) {
+    //         console.error(error);
+    //     }
+    // },
+
+    // stopDiscovery: async function() {
+    //     try {
+    //         await bleManager.stopBluetoothDevicesDiscovery();
+    //         console.log('停止搜索成功');
+    //     } catch (error) {
+    //         console.error(error);
+    //     }
+    // },
+
+    // readCharacteristicValue: async function(deviceId, serviceId, characteristicId) {
+    //     try {
+    //         const value = await bleManager.readCharacteristicValue(deviceId, serviceId, characteristicId);
+    //         console.log('读取特征值:', value);
+    //     } catch (error) {
+    //         console.error(error);
+    //     }
+    // },
+
+    // notifyCharacteristicValueChange: async function(deviceId, serviceId, characteristicId, state) {
+    //     try {
+    //         const result = await bleManager.notifyCharacteristicValueChange(deviceId, serviceId, characteristicId, state);
+    //         console.log('通知特征值变化:', result);
+    //     } catch (error) {
+    //         console.error(error);
+    //     }
+    // }
+
+    checkDevice() {
+        this.send(BtCmd.checkDevice);
+        //2.0有发这个,不知道是啥
+        this.send(Uint8Array.from([0x54, 0x44, 0x44, 0x48, 0x01, 0x09, 0x26, 0x01, 0x01]));
+    }
+
+    getVersion() {
+        this.send(BtCmd.queryVersion);
+    }
+
+    static get _time() {
+        return 400;
+    }
+
+    async getDeviceInfo() {
+        // if (DeviceManager.instance.isEarphone) {
+        //     this.send(BtCmd.queryKwhEarPhone);
+        //     this.send(BtCmd.queryLowDelayMode);
+        //     this.send(BtCmd.queryEQ);
+        //     this.send(BtCmd.queryLowPower);
+        //     this.send(BtCmd.queryCtrlStatus);
+        // } else {
+        await this.send(BtCmd.setTime);
+        await this.send(BtCmd.queryKwh);
+        await this.getSleep();
+        await this.getAlert();
+        await this.send(BtCmd.queryRGB);
+        await this.getVolume();
+        await this.getPauseSleep();
+        // }
+    }
+
+    getAlert() {
+        this.send(BtCmd.queryAlarm);
+    }
+
+    getSleep() {
+        this.send(BtCmd.querySleep);
+    }
+
+    setAlert(open, weekCycle, hour, minutes, channel = 1) {
+        this.send(BtCmd.setAlarm({ switchStatus: open, weekCycle, hour, minutes }));
+    }
+
+    setAutoPlay(open) {
+        this.send(BtCmd.setAutoPlay({ switchStatus: open }));
+    }
+
+    getAutoPlay() {
+        this.send(BtCmd.getAutoPlay);
+    }
+
+    setLowKwhWarningTone(notify) {
+        this.send(BtCmd.setLowKwhWarningTone({ notify }));
+    }
+
+    setPauseSleep(time) {
+        this.send(BtCmd.setSleepAfterPlayPause({ time }));
+    }
+
+    getPauseSleep() {
+        this.send(BtCmd.querySleepAfterPlayPause);
+    }
+
+    setRGB(r, g, b) {
+        this.send(BtCmd.setRGB({ r, g, b }));
+    }
+
+    setSleep(open, hour, minutes) {
+        this.send(BtCmd.setSleep({ switchStatus: open, hour, minutes }));
+    }
+
+    setTime() {
+        this.send(BtCmd.setTime);
+    }
+
+    async setVolume(volume) {
+        VolumeUtil.setVolume(volume, { hideVolumeView: true });
+
+        const phoneMax = await VolumeUtil.getMaxVolume();
+        const result = Math.floor(CmdBase.volumeMax * volume / phoneMax);
+
+        console.log(`phoneMax=${phoneMax}, deviceMax=${CmdBase.volumeMax}, setVolume=${volume}, result=${result}`);
+
+        this.send(BtCmd.setVolume({ volume: result }));
+    }
+
+    getVolume() {
+        this.send(BtCmd.queryVolume);
+    }
+
+    stop() {
+        // EventManager.fire({ event: 'playStatus', playStatus: EnumPlayStatus.stop.index });
+    }
+
+    next() {
+        // TODO: implement next
+    }
+
+    pause() {
+        // EventManager.fire({ event: 'playStatus', playStatus: EnumPlayStatus.pause.index });
+    }
+
+    play() {
+        // TODO: implement play
+    }
+
+    previous() {
+        // TODO: implement previous
+    }
+
+    startVoiceRecordResponse(supplier) {
+        console.log(`回调开始录音:${supplier}`);
+        // switch (supplier) {
+        //     case EnumSupplier.jieLi:
+        //         this.send(BtCmd.jlStartVoiceResponse);
+        //         break;
+        //     case EnumSupplier.lingXin:
+        //         this.send(BtCmd.lxStartVoiceResponse);
+        //         break;
+        //     case EnumSupplier.qiXinWei:
+        //         this.send(BtCmd.b1StartVoiceResponse);
+        //         break;
+        // }
+    }
+
+    stopVoiceRecordResponse(supplier) {
+        console.log(`回调结束录音:${supplier}`);
+        switch (supplier) {
+            case EnumSupplier.jieLi:
+                this.send(BtCmd.jlStopVoiceResponse);
+                break;
+            case EnumSupplier.lingXin:
+                this.send(BtCmd.lxStopVoiceResponse);
+                break;
+            case EnumSupplier.qiXinWei:
+                this.send(BtCmd.b1StopVoiceResponse);
+                break;
+        }
+    }
+
+    getLowDelayMode() {
+        this.send(BtCmd.queryLowDelayMode);
+    }
+
+    setLowDelayMode(open, mode) {
+        this.send(BtCmd.setLowDelayMode({ open, mode }));
+    }
+
+    setLowPowerMode(isOpen) {
+        this.send(BtCmd.setLowPowerMode({ isOpen: isOpen ? EnumOpen.open : EnumOpen.close }));
+    }
+
+    queryLowPower() {
+        this.send(BtCmd.queryLowPower);
+    }
+
+    setCtrlStatus(singleClick, doubleClick, longClick) {
+        this.send(BtCmd.setCtrlStatus({ singleClick, doubleClick, longClick }));
+    }
+
+    getEQ() {
+        this.send(BtCmd.queryEQ);
+    }
+
+    setEQ(list) {
+        this.send(BtCmd.setEQ({ list }));
+    }
+
+    queryLowKwhWarningTone() {
+        this.send(BtCmd.queryLowKwhWarningTone);
+    }
+
+    async changeChannelCallBack() {
+        await this.send(Uint8Array.from([0x54, 0x44, 0x44, 0x48, 0x01, 0x0a, 0x29, 0x02, 0x00, 0x01]));
+        await this.send(Uint8Array.from([0x54, 0x44, 0x44, 0x48, 0x01, 0x09, 0x39, 0x01, 0x00]));
+    }
+}
+
+// 导出 BtHelper 类
+const btHelper = new BtHelper();
+
+module.exports = btHelper;

+ 355 - 0
devices/cmd_key_event.js

@@ -0,0 +1,355 @@
+// 枚举定义
+const EnumCmdEvent = {
+    nextChannel: 'nextChannel',
+    previousChannel: 'previousChannel',
+    switchChannel: 'switchChannel',
+    previousAlbum: 'previousAlbum',
+    nextAlbum: 'nextAlbum',
+    nextSong: 'nextSong',
+    previousSong: 'previousSong',
+    speed: 'speed',
+    fastback: 'fastback',
+    stopSpeed: 'stopSpeed',
+    stopFastback: 'stopFastback',
+    startRecord: 'startRecord',
+    stopRecord: 'stopRecord',
+    battery: 'battery',
+    lowKwnSet: 'lowKwnSet',
+    batteryEarphone: 'batteryEarphone',
+    ctrlStatus: 'ctrlStatus',
+    lowPowerOpen: 'lowPowerOpen',
+    eq: 'eq',
+    lowDelayMode: 'lowDelayMode',
+    sleep: 'sleep',
+    wake: 'wake',
+    volume: 'volume',
+    version: 'version',
+    sim: 'sim',
+    dsn: 'dsn',
+    playInfo: 'playInfo',
+    playStatus: 'playStatus',
+    auth: 'auth',
+    unbind: 'unbind',
+    enableTTS: 'enableTTS',
+    switchDeviceMode: 'switchDeviceMode',
+    netModeAuto: 'netModeAuto',
+    getDeviceInfo: 'getDeviceInfo',
+    collectState: 'collectState',
+    todayStep: 'todayStep',
+    todayRates: 'todayRates',
+    targetSteps: 'targetSteps',
+    setTimeFormat: 'setTimeFormat',
+    setNotDisturb: 'setNotDisturb',
+    setQuickViewTime: 'setQuickViewTime',
+    openPush: 'openPush',
+    takeHandLight: 'takeHandLight',
+    payId: 'payId',
+    authSleepStatus: 'authSleepStatus',
+    btMac: 'btMac'
+};
+
+// 枚举定义
+const EnumPlayStatus = {
+    play: 'play',
+    pause: 'pause',
+    stop: 'stop',
+    completed: 'completed'
+};
+
+// 设备操作类
+class CmdEvent {
+    constructor({ cmdEvent }) {
+        this.cmdEvent = cmdEvent;
+        this.channelId = 1;
+        this.wakeSwitch = null;
+        this.wakeCycle = [0, 0, 0, 0, 0, 0, 0];
+        this.ctrlStatus = [0, 0, 0];
+        this.lowPowerOpen = null;
+        this.wakeHour = null;
+        this.wakeMinutes = null;
+        this.sleepSwitch = null;
+        this.sleepHour = null;
+        this.sleepMinutes = null;
+        this.kwh = 9;
+        this.kwhLeft = 9;
+        this.kwhRight = 9;
+        this.kwhBox = 9;
+        this.lowKwnSet = false;
+        this.volume = 0;
+        this.version = "1.0.0";
+        this.dsn = null;
+        this.playStatus = null;
+        this.playInfo = null;
+        this.authInfo = null;
+        this.item = null;
+        this.eSim = null;
+        this.sim = null;
+        this.simIndex = null;
+        this.enableTTS = null;
+        this.eqs = null;
+        this.lowDelayMode = null;
+        this.lowDelayModeOpen = null;
+        this.netModeAuto = null;
+        this.pauseSleep = null;
+        this.collectStatus = null;
+        this.audioInfoId = null;
+        this.todayStep = null;
+        this.todayRates = null;
+        this.stepTarget = null;
+        this.setTimeFormat = null;
+        this.setQuickViewTime = null;
+        this.setNotDisturb = null;
+        this.openPush = null;
+        this.takeHandLight = null;
+        this.stepDistance = null;
+        this.stepCalories = null;
+        this.stepTime = null;
+        this.payId = null;
+        this.btMac = null;
+        this.deviceMode = null;
+    }
+
+    static wake({ wakeSwitch, wakeCycle, wakeHour, wakeMinutes }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.wake });
+        event.wakeSwitch = wakeSwitch;
+        event.wakeCycle = wakeCycle;
+        event.wakeHour = wakeHour;
+        event.wakeMinutes = wakeMinutes;
+        return event;
+    }
+
+    static sim({ eSim, sim, simIndex }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.sim });
+        event.eSim = eSim;
+        event.sim = sim;
+        event.simIndex = simIndex;
+        return event;
+    }
+
+    static sleep({ sleepSwitch, sleepHour, sleepMinutes }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.sleep });
+        event.sleepSwitch = sleepSwitch;
+        event.sleepHour = sleepHour;
+        event.sleepMinutes = sleepMinutes;
+        return event;
+    }
+
+    static ctrlStatus({ ctrlStatus }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.ctrlStatus });
+        event.ctrlStatus = ctrlStatus;
+        return event;
+    }
+
+    static switchChannel({ channelId }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.switchChannel });
+        event.channelId = channelId;
+        return event;
+    }
+
+    static switchDeviceMode({ deviceMode }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.switchDeviceMode });
+        event.deviceMode = deviceMode;
+        return event;
+    }
+
+    static enableTTS({ enableTTS }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.enableTTS });
+        event.enableTTS = enableTTS;
+        return event;
+    }
+
+    static battery({ kwh }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.battery });
+        event.kwh = kwh;
+        return event;
+    }
+
+    static queryLowKwn({ kwh }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.lowKwnSet });
+        event.kwh = kwh;
+        return event;
+    }
+
+    static dsn({ dsn }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.dsn });
+        event.dsn = dsn;
+        return event;
+    }
+
+    static getDeviceInfo() {
+        return new CmdEvent({ cmdEvent: EnumCmdEvent.getDeviceInfo });
+    }
+
+    static todayStep({ todayStep, stepDistance, stepCalories, stepTime }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.todayStep });
+        event.todayStep = todayStep;
+        event.stepDistance = stepDistance;
+        event.stepCalories = stepCalories;
+        event.stepTime = stepTime;
+        return event;
+    }
+
+    static todaySport({ todayStep, stepDistance, stepCalories, stepTime }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.todayStep });
+        event.todayStep = todayStep;
+        event.stepDistance = stepDistance;
+        event.stepCalories = stepCalories;
+        event.stepTime = stepTime;
+        return event;
+    }
+
+    static todayRates({ todayRates }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.todayRates });
+        event.todayRates = todayRates;
+        return event;
+    }
+
+    static pauseSleep({ pauseSleep }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.authSleepStatus });
+        event.pauseSleep = pauseSleep;
+        return event;
+    }
+
+    static targetSteps({ stepTarget }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.targetSteps });
+        event.stepTarget = stepTarget;
+        return event;
+    }
+
+    static setTimeFormat({ setTimeFormat }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.setTimeFormat });
+        event.setTimeFormat = setTimeFormat;
+        return event;
+    }
+
+    static setNotDisturb({ setNotDisturb }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.setNotDisturb });
+        event.setNotDisturb = setNotDisturb;
+        return event;
+    }
+
+    static setQuickViewTime({ setQuickViewTime }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.setQuickViewTime });
+        event.setQuickViewTime = setQuickViewTime;
+        return event;
+    }
+
+    static openPush({ openPush }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.openPush });
+        event.openPush = openPush;
+        return event;
+    }
+
+    static takeHandLight({ takeHandLight }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.takeHandLight });
+        event.takeHandLight = takeHandLight;
+        return event;
+    }
+
+    static batteryEarphone({ kwh, kwhLeft, kwhRight, kwhBox }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.batteryEarphone });
+        event.kwh = kwh;
+        event.kwhLeft = kwhLeft;
+        event.kwhRight = kwhRight;
+        event.kwhBox = kwhBox;
+        return event;
+    }
+
+    static unbind({ item }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.unbind });
+        event.item = item;
+        return event;
+    }
+
+    static auth({ authInfo }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.auth });
+        event.authInfo = authInfo;
+        return event;
+    }
+
+    static eqs({ eqs }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.eq });
+        event.eqs = eqs;
+        return event;
+    }
+
+    static lowDelayMode({ lowDelayMode, lowDelayModeOpen }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.lowDelayMode });
+        event.lowDelayMode = lowDelayMode;
+        event.lowDelayModeOpen = lowDelayModeOpen;
+        return event;
+    }
+
+    static lowPowerOpen({ lowPowerOpen }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.lowPowerOpen });
+        event.lowPowerOpen = lowPowerOpen;
+        return event;
+    }
+
+    static volume({ volume }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.volume });
+        event.volume = volume;
+        return event;
+    }
+
+    static version({ version }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.version });
+        event.version = version;
+        return event;
+    }
+
+    static btMac({ btMac }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.btMac });
+        event.btMac = btMac;
+        return event;
+    }
+
+    static netModeAuto({ netModeAuto }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.netModeAuto });
+        event.netModeAuto = netModeAuto;
+        return event;
+    }
+
+    static playInfo({ playInfo }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.playInfo });
+        event.playInfo = playInfo;
+        return event;
+    }
+
+    static payId({ payId }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.payId });
+        event.payId = payId;
+        return event;
+    }
+
+    static collectState({ collectStatus, audioInfoId }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.collectState });
+        event.collectStatus = collectStatus;
+        event.audioInfoId = audioInfoId;
+        return event;
+    }
+
+    static playStatus({ playStatus }) {
+        const event = new CmdEvent({ cmdEvent: EnumCmdEvent.playStatus });
+        switch (playStatus) {
+            case 1:
+                event.playStatus = EnumPlayStatus.play;
+                break;
+            case 2:
+                event.playStatus = EnumPlayStatus.pause;
+                break;
+            case 3:
+                event.playStatus = EnumPlayStatus.stop;
+                break;
+            case 4:
+                event.playStatus = EnumPlayStatus.completed;
+                break;
+            default:
+        }
+        return event
+    }
+
+    toString() {
+        return `CmdEvent{cmdEvent: ${this.cmdEvent}, channelId: ${this.channelId}, wakeSwitch: ${this.wakeSwitch}, wakeCycle: ${this.wakeCycle}, wakeHour: ${this.wakeHour}, wakeMinutes: ${this.wakeMinutes}, sleepSwitch: ${this.sleepSwitch}, sleepHour: ${this.sleepHour}, sleepMinutes: ${this.sleepMinutes}, kwh: ${this.kwh}, volume: ${this.volume}, version: ${this.version}}`;
+    }
+}

+ 49 - 0
devices/connect_event.js

@@ -0,0 +1,49 @@
+// 枚举定义
+const EnumConnectStatus = {
+    connecting: 'connecting',
+    connected: 'connected',
+    disconnected: 'disconnected',
+    connectFail: 'connectFail'
+};
+
+// todo 设备实体类
+class DeviceBean {
+    // 假设 DeviceBean 有以下属性
+    constructor({ id, name, type }) {
+        this.id = id;
+        this.name = name;
+        this.type = type;
+    }
+
+    toString() {
+        return `DeviceBean{id: ${this.id}, name: ${this.name}, type: ${this.type}}`;
+    }
+}
+
+// 连接事件类
+class ConnectEvent {
+    constructor({ status, device }) {
+        this.status = status;
+        this.device = device;
+    }
+
+    static connecting() {
+        return new ConnectEvent({ status: EnumConnectStatus.connecting });
+    }
+
+    static success() {
+        return new ConnectEvent({ status: EnumConnectStatus.connected });
+    }
+
+    static disconnect() {
+        return new ConnectEvent({ status: EnumConnectStatus.disconnected });
+    }
+
+    static fail() {
+        return new ConnectEvent({ status: EnumConnectStatus.connectFail });
+    }
+
+    toString() {
+        return `ConnectEvent{status: ${this.status}, device: ${this.device ? this.device.toString() : 'null'}}`;
+    }
+}