| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- /**
- * BLE 蓝牙连接与设备控制 mixin
- * 基于 Pinia Store 管理蓝牙单例状态
- * 负责:权限检查、连接/断连/重连、指令收发、设备状态管理
- */
- import { useBleStore } from '@/stores/ble'
- import { MODE, MOXI_STATE, POWER, MUTE, TEMPERATURE, CHAIR_ANGLE } from '@/utils/ble/constants.js'
- import { ensureBlePrerequisite, openBluetoothSettings, openLocationSettings } from '@/utils/ble/permission.js'
- export default {
- data() {
- return {
- isShowConfirm: false
- }
- },
- computed: {
- bleStore() {
- return useBleStore()
- },
- linked() {
- return this.bleStore.linked
- },
- deviceStatus: {
- get() { return this.bleStore.deviceStatus },
- set(val) { this.bleStore.deviceStatus = val }
- },
- hotPercentage() {
- return this.bleStore.hotPercentage
- },
- ispreHot: {
- get() { return this.bleStore.ispreHot },
- set(val) { this.bleStore.ispreHot = val }
- },
- reconnectDrawer() {
- return this.bleStore.reconnectDrawer
- },
- reconnectCount() {
- return this.bleStore.reconnectCount
- },
- excepDrawer: {
- get() { return this.bleStore.excepDrawer },
- set(val) { this.bleStore.excepDrawer = val }
- },
- exceTxt: {
- get() { return this.bleStore.exceTxt },
- set(val) { this.bleStore.exceTxt = val }
- }
- },
- methods: {
- // ========== BLE 初始化 ==========
- async initBle() {
- // 1. Android权限检查
- try {
- await ensureBlePrerequisite()
- } catch (e) {
- if (e.message === 'BLE_PERMISSION_DENIED') {
- this.$refs.ayToast.error('请授予蓝牙权限后重试')
- return
- }
- if (e.message === 'BLE_LOCATION_OFF') {
- this.$refs.ayToast.error('请开启位置服务后重试')
- return
- }
- if (e.message === 'BLE_ADAPTER_OFF') {
- uni.showModal({
- title: '提示',
- content: '蓝牙未开启,请先开启蓝牙',
- cancelText: '取消',
- confirmText: '去开启',
- success: (res) => {
- if (res.confirm) {
- openBluetoothSettings()
- }
- }
- })
- return
- }
- }
- // 2. 判断当前是否已连接同一设备,如果是则跳过重连
- // 使用 advertisData(= 后台 deviceCode)作为跨平台设备唯一标识
- const currentDevice = this.bleStore.device
-
- console.log('[BLE] 当前设备', currentDevice, this.bleStore.linked, this.advertisData)
- if (this.bleStore.linked && currentDevice && this.advertisData && currentDevice.advertisData === this.advertisData) {
- console.log('[BLE] 已连接相同设备(advertisData匹配),跳过重连', this.advertisData)
- return
- }
- // 3. 不同设备或未连接:断开旧连接并关闭适配器(不解绑全局监听器)
- try { await this.bleStore.disconnect() } catch (_) {}
- try { await this.bleStore.resetAdapter() } catch (_) {}
- // 4. 扫描并连接设备
- // 注意:this.deviceId 是后台数据库ID,不是BLE设备ID,不能用于直连
- // 优先从缓存中获取上次成功连接的真实BLE deviceId
- try {
- let realBleDeviceId = ''
- if (this.advertisData) {
- // 优先从bleStore当前设备获取(可能连接已断但device对象仍在)
- if (currentDevice && currentDevice.advertisData === this.advertisData && currentDevice.deviceId) {
- realBleDeviceId = currentDevice.deviceId
- }
- // 其次从本地缓存获取
- if (!realBleDeviceId) {
- try {
- realBleDeviceId = uni.getStorageSync('ble_deviceId_' + this.advertisData) || ''
- console.log("当前设备id",realBleDeviceId)
- } catch (_) {}
- }
- }
- const scanOpt = { timeout: 8000 }
- // 用真实BLE deviceId直连,避免扫描(减少Android限流风险)
- if (realBleDeviceId) {
- scanOpt.deviceId = realBleDeviceId
- }
- if (this.deviceName) {
- scanOpt.deviceName = this.deviceName
- }
- // 传递 advertisData 用于连接后存储到 device 对象
- if (this.advertisData) {
- scanOpt.advertisData = this.advertisData
- }
- console.log("当前连接蓝牙参数",scanOpt)
- await this.bleStore.scanAndConnect(scanOpt)
- // 连接成功后缓存真实BLE deviceId,供下次直连使用
- if (this.advertisData && this.bleStore.device && this.bleStore.device.deviceId) {
- try {
- uni.setStorageSync('ble_deviceId_' + this.advertisData, this.bleStore.device.deviceId)
- } catch (_) {}
- }
- // BLE连接建立后需短暂等待设备就绪再发首条指令
- await new Promise(r => setTimeout(r, 1500))
- this._sendCurrentState()
- } catch (e) {
- console.error('BLE连接失败', e)
- this.$refs.ayToast.error(this._bleFriendlyMsg(e))
- }
- },
- // ========== 发送指令 ==========
- _sendCurrentState() {
- const modeMap = [MODE.LEISURE, MODE.PROFESSIONAL, MODE.PERSONAL, MODE.EXPERT]
- this.bleStore.sendBasic({
- power: POWER.ON,
- moxiState: MOXI_STATE.DONE,
- preheatState: MOXI_STATE.DONE,
- mute: MUTE.OFF,
- mode: modeMap[this.modeType] || MODE.LEISURE,
- subMode: 0x01,
- temperature: TEMPERATURE.MID,
- angle: CHAIR_ANGLE.OFF,
- duration: 0
- }).catch(e => console.error('sendBasic fail', e))
- },
- // ========== 开始艾灸 ==========
- startDeviceEvt() {
- this.bleStore.deviceStatus = 1
- this.bleStore.ispreHot = false
- let totalDuration = 0
- this.curCase.forEach(item => {
- totalDuration += item.time
- })
- const modeMap = [MODE.LEISURE, MODE.PROFESSIONAL, MODE.PERSONAL, MODE.EXPERT]
- this.bleStore.sendBasic({
- power: POWER.ON,
- moxiState: MOXI_STATE.START,
- preheatState: MOXI_STATE.START,
- mute: MUTE.OFF,
- mode: modeMap[this.modeType] || MODE.LEISURE,
- subMode: 0x01,
- temperature: TEMPERATURE.MID,
- angle: CHAIR_ANGLE.OFF,
- duration: totalDuration || 30
- }).catch(e => console.error('startDevice fail', e))
- if (this.curCase.length > 0) {
- const points = this.curCase.map(item => ({
- point: item.id,
- x: item._x,
- y: item._y
- }))
- this.bleStore.sendAcupoints(points).catch(e => console.error('sendAcupoints fail', e))
- }
- },
- // ========== 暂停/继续 ==========
- stopAijiu() {
- if (this.deviceStatus == 5) {
- this.bleStore.deviceStatus = 3
- this.bleStore.sendBasic({ moxiState: MOXI_STATE.START }).catch(() => {})
- } else if (this.deviceStatus == 3) {
- this.bleStore.deviceStatus = 5
- this.bleStore.sendBasic({ moxiState: MOXI_STATE.PAUSE }).catch(() => {})
- }
- },
- // ========== 停止 ==========
- stopPreHot() {
- this.isShowConfirm = true
- },
- confirmStop() {
- this.bleStore.deviceStatus = 0
- this.isShowConfirm = false
- this.bleStore.sendBasic({ moxiState: MOXI_STATE.DONE }).catch(() => {})
- },
- // ========== 模式切换 ==========
- changeMode(num) {
- uni.showModal({
- title: '提示',
- content: '切换模式会停止当前的艾灸,是否继续',
- cancelText: '取消',
- confirmText: '继续',
- success: (res) => {
- if (res.confirm) {
- this.bleStore.modeType = num
- this.bleStore.deviceStatus = 0
- this.isShowDrawer2 = false
- if (this._stopAudioOnModeChange) {
- this._stopAudioOnModeChange()
- }
- const modeMap = [MODE.LEISURE, MODE.PROFESSIONAL, MODE.PERSONAL, MODE.EXPERT]
- this.bleStore.sendBasic({
- power: POWER.ON,
- moxiState: MOXI_STATE.DONE,
- preheatState: MOXI_STATE.DONE,
- mute: MUTE.OFF,
- mode: modeMap[num] || MODE.LEISURE,
- subMode: 0x01,
- temperature: TEMPERATURE.MID,
- angle: CHAIR_ANGLE.OFF,
- duration: 0
- }).catch(e => console.error('changeMode fail', e))
- }
- }
- })
- },
- // ========== 断开BLE ==========
- disconnectBle() {
- // 先停止扫描(防止页面销毁后 onBluetoothDeviceFound 回调找不到 taskCenter)
- this.bleStore.stopScan()
- this.bleStore.disconnect().catch(() => {})
- },
- /**
- * 页面离开时的轻量清理:只停止扫描,不断开BLE连接
- * 保持蓝牙连接状态,下次进入相同设备页面可复用
- */
- cleanupOnLeave() {
- this.bleStore.stopScan()
- },
- // ========== BLE错误码转用户友好提示 ==========
- _bleFriendlyMsg(e) {
- const code = e && (e.code || e.message || '')
- const originMsg = e && e.origin && e.origin.msg
- const map = {
- BLE_SCAN_FAIL: this.bleStore.scanThrottled
- ? '扫描过于频繁,请等待30秒后再试'
- : (originMsg || '未搜索到设备,请确保设备已开机并靠近手机'),
- BLE_CONNECT_FAIL: '连接设备失败,请确保设备在范围内并重试',
- BLE_CONNECT_TIMEOUT: '连接超时,请靠近设备后重试',
- BLE_ADAPTER_OFF: '蓝牙未开启,请先开启蓝牙',
- BLE_PERMISSION_DENIED: '蓝牙权限未授予,请在设置中开启',
- BLE_LOCATION_OFF: '位置服务未开启,请开启后重试',
- BLE_DISCONNECTED: '设备已断开连接',
- BLE_SERVICE_NOT_FOUND: '设备服务异常,请重启设备后重试',
- BLE_WRITE_FAIL: '指令发送失败,请重试',
- BLE_BUSY: '设备正忙,请稍后再试'
- }
- return map[code] || ('连接失败,请重试 (' + code + ')')
- }
- }
- }
|