import { get } from '../utils/request/index' import type { RawDevice, LeFuPlugin, ScannedDevice, LeFuWifiItem, ProgressData, LockData, WifiVersion, LeFuConfig, DeviceSetting, DeviceMember, DeviceInfo, FirmwareVersion, } from './types' /** 乐福 SDK 配置 */ const LEFU_CONFIG: LeFuConfig = { key: 'lefudc91611833e18d94', secret: 'eQ50JYimMp0X7uhNLj6D3lTEk4TUUvEG7dvaqKM9t1U=', domain1: 'http://192.168.1.31:8889', // 测试环境(老版本 无鉴权) domain2: 'http://192.168.1.31:8889/weight', // 测试环境(新版本 有鉴权) // domain1: 'http://device.shuziweidao.com:80/gateway', // 正式环境 // domain2: 'http://device.shuziweidao.com:80/weight', // 正式环境 } /** 支持的设备型号配置列表(5款) */ const DEVICE_SETTINGS: DeviceSetting[] = [ { advLength: 999, calorieStatus: 0, createBy: null, deviceAccuracyType: 2, deviceCalcuteType: 3, deviceConnectType: 2, deviceFuncType: 223, deviceName: 'CF568_G', devicePowerType: 3, deviceProtocolType: 3, deviceType: 1, deviceUnitType: '0,1,11', id: 65, imgUrl: null, macAddressStart: 6, remark: null, sign: 'FF', status: 0, uhStatus: 1, updateBy: null, }, { advLength: 999, calorieStatus: 0, createBy: null, deviceAccuracyType: 2, deviceCalcuteType: 3, deviceConnectType: 2, deviceFuncType: 223, deviceName: 'YX-B4-568-BW', devicePowerType: 3, deviceProtocolType: 3, deviceType: 1, deviceUnitType: '0,1,11', id: 65, imgUrl: null, macAddressStart: 6, remark: null, sign: 'FF', status: 0, uhStatus: 1, updateBy: null, }, { advLength: 999, calorieStatus: 0, createBy: null, deviceAccuracyType: 2, deviceCalcuteType: 4, deviceConnectType: 2, deviceFuncType: 65759, deviceName: 'CF636_G', devicePowerType: 3, deviceProtocolType: 3, deviceType: 1, deviceUnitType: '0,1,2', id: 216, imgUrl: null, macAddressStart: 6, remark: null, sign: 'FF', status: 0, uhStatus: 1, updateBy: null, }, { advLength: 999, calorieStatus: 0, createBy: null, deviceAccuracyType: 2, deviceCalcuteType: 4, deviceConnectType: 2, deviceFuncType: 65759, deviceName: 'YX-B8-636-BW', devicePowerType: 3, deviceProtocolType: 3, deviceType: 1, deviceUnitType: '0,1,2', id: 216, imgUrl: null, macAddressStart: 6, remark: null, sign: 'FF', status: 0, uhStatus: 1, updateBy: null, }, { advLength: 999, calorieStatus: 0, createBy: null, deviceAccuracyType: 2, deviceCalcuteType: 4, deviceConnectType: 2, deviceFuncType: 223, deviceName: 'CF577', devicePowerType: 3, deviceProtocolType: 3, deviceType: 1, deviceUnitType: '0,1,11', id: 51, imgUrl: null, macAddressStart: 6, remark: null, sign: 'FF', status: 0, uhStatus: 1, updateBy: null, }, ] const TAG = '[LeFuService]' class LeFuService { private _plugin: LeFuPlugin | null = null /** 设备协议对象,devicesModel 事件返回,用于 WiFi 配网等高级操作 */ private _activeProtocol: any = null /** 重连次数 */ private _reconnectCount = 0 private readonly _maxReconnect = 3 private _reconnectTimer: number | null = null /** 保活心跳定时器 */ private _keepAliveTimer: number | null = null /** 连接后从 deviceInfo 事件获取的固件信息 */ private _deviceInfo: DeviceInfo | null = null /** bus 事件是否已订阅(防止多次 startScan 重复 subscribe 累积) */ private _busSubscribed = false /** 回调注册(页面通过 on* 方法注册,stopScan 时清空) */ private _onDevicesListCb: ((devices: ScannedDevice[]) => void) | null = null private _onConnectStateCb: ((state: string) => void) | null = null private _onDeviceConnectCb: (() => void) | null = null private _onProgressCb: ((data: ProgressData) => void) | null = null private _onLockedCb: ((data: LockData) => void) | null = null private _onDisconnectedCb: (() => void) | null = null private _onDeviceInfoCb: ((info: DeviceInfo) => void) | null = null /** 在 app.ts onLaunch 中调用,加载 ppScale-plugin */ init(): void { console.log(TAG, 'init() 开始加载插件') this._plugin = requirePlugin('ppScale-plugin') as LeFuPlugin console.log(TAG, 'init() 插件加载完成:', !!this._plugin) } /** 获取插件(未初始化时抛出) */ private get _p(): LeFuPlugin { if (!this._plugin) throw new Error('[LeFu] 服务未初始化,请先调用 init()') return this._plugin } /** 乐福配置(供体成分分析等外部使用) */ get config(): LeFuConfig { return LEFU_CONFIG } // ─── 回调注册 ───────────────────────────────── /** 注册设备列表回调(扫描到设备时触发) */ onDevicesList(cb: (devices: ScannedDevice[]) => void): void { console.log(TAG, 'onDevicesList 回调已注册') this._onDevicesListCb = cb } /** 注册蓝牙连接状态回调 */ onConnectState(cb: (state: string) => void): void { console.log(TAG, 'onConnectState 回调已注册') this._onConnectStateCb = cb } /** 注册设备连接就绪回调(activeProtocol 可用时触发) */ onDeviceConnect(cb: () => void): void { console.log(TAG, 'onDeviceConnect 回调已注册') this._onDeviceConnectCb = cb } /** 当前蓝牙连接状态(connectState bus 事件实时更新) */ private _connectState = '' /** 设备是否已连接(activeProtocol 就绪) */ get isConnected(): boolean { return !!this._activeProtocol } /** 当前蓝牙连接状态字符串 */ get connectState(): string { return this._connectState } /** 插件 BLUE_STATE 常量对象(供页面 wxml 条件判断用) */ get BLUE_STATE(): Record { return this._plugin?.BLUE_STATE ?? {} } /** 连接后从 deviceInfo 事件获取的设备信息(含 modelNumber / serialNumber 等) */ get deviceInfo(): DeviceInfo | null { return this._deviceInfo } /** 注册实时测量回调(测量过程中多次触发) */ onMeasuring(cb: (data: ProgressData) => void): void { this._onProgressCb = cb } /** 注册测量完成回调(数据锁定,触发一次) */ onLocked(cb: (data: LockData) => void): void { this._onLockedCb = cb } /** 注册设备断开回调 */ onDisconnected(cb: () => void): void { this._onDisconnectedCb = cb } /** 注册 deviceInfo 就绪回调(设备名/固件信息可用时触发) */ onDeviceInfo(cb: (info: DeviceInfo) => void): void { this._onDeviceInfoCb = cb } // ─── 扫描与连接 ─────────────────────────────── /** * 开始蓝牙扫描 * 对齐 master:先 setDeviceSetting → start,再 subscribe(master searchDevice.js 顺序) */ startScan(): void { console.log(TAG, 'startScan() 开始,busSubscribed:', this._busSubscribed) const plugin = this._p // ① 先设置设备配置并启动扫描(必须在 subscribe 之前,与 master 一致) plugin.Blue.setDeviceSetting(DEVICE_SETTINGS) const deviceNames = DEVICE_SETTINGS.map(s => s.deviceName) console.log(TAG, 'plugin.Blue.start() 调用,设备名称:', deviceNames) plugin.Blue.start(deviceNames, false) // bus 订阅只注册一次,stopScan 时重置,避免多次搜索导致订阅累积 if (this._busSubscribed) { console.log(TAG, 'bus 已订阅,跳过重复注册') return } this._busSubscribed = true // ② 启动后再订阅设备列表(master 也是 start 后才 subscribe) plugin.bus.subscribe('devicesList', (devList: RawDevice[]) => { console.log(TAG, 'bus[devicesList] 触发,原始设备数:', devList?.length, devList?.map((d: RawDevice) => d.name || d.deviceName)) if (!devList || !devList.length) { this._onDevicesListCb?.([]) return } // 直接用原始列表转换,不调用 getDeviceModel(master 也是直接传 res) const devices: ScannedDevice[] = devList.map((item: RawDevice) => ({ name: item.name || item.deviceName || '未知设备', raw: item, model: {}, })) console.log(TAG, '转换后设备列表:', devices.map(d => d.name)) this._onDevicesListCb?.(devices) }) // 设备模型信息(含 mac 等,仅用于日志/调试) plugin.bus.subscribe('devicesModel', (res: any) => { console.log(TAG, 'bus[devicesModel] 触发,deviceMac:', res?.deviceMac) }) // 连接成功后获取固件信息(用于后续 OTA 版本比对) plugin.bus.subscribe('deviceInfo', (res: DeviceInfo) => { console.log(TAG, 'bus[deviceInfo] 触发:',res, res?.firmwareRevision, res?.modelNumber) this._deviceInfo = res this._onDeviceInfoCb?.(res) }) // 连接建立:从 ScaleAction 获取 activeProtocol,启动数据进度(对齐 master configureDevice.js) plugin.bus.subscribe('deviceConnect', () => { console.log(TAG, 'bus[deviceConnect] 触发,获取 activeProtocol,启动数据进度') this._reconnectCount = 0 plugin.ScaleAction.startDataProgress(true) this._activeProtocol = plugin.ScaleAction.getActiveProtocol() this._connectState = plugin.BLUE_STATE.CONNECTSUCCESS this._onDeviceConnectCb?.() }) // 连接状态变化:CONNECTSUCCESS 时启动保活,CONNECTFAILED 时重连 plugin.bus.subscribe('connectState', (res: string) => { console.log(TAG, 'bus[connectState] 触发:', res) this._connectState = res this._onConnectStateCb?.(res) if (res === plugin.BLUE_STATE.CONNECTSUCCESS) { console.log(TAG, '连接成功,启动保活心跳') this._startKeepAlive() } if (res === plugin.BLUE_STATE.CONNECTFAILED) { console.warn(TAG, '连接失败,触发重连逻辑') this._stopKeepAlive() this._doReconnect() } }) // 实时测量数据 plugin.bus.subscribe('progressData', (res: ProgressData) => { console.log(TAG, 'bus[progressData] 体重(g):', res?.weight) this._onProgressCb?.(res) }) // 测量锁定(完成) plugin.bus.subscribe('lockData', (res: LockData) => { console.log(TAG, 'bus[lockData] 测量完成,体重(g):', res?.weight, '阻抗:', res?.resistance) this._onLockedCb?.(res) }) // 设备自动断开 plugin.bus.subscribe('deviceWillDisconnect', () => { console.warn(TAG, 'bus[deviceWillDisconnect] 设备断开,触发重连') this._stopKeepAlive() this._onDisconnectedCb?.() this._doReconnect() }) } /** * 主动连接指定设备(对齐 master:直接调 createBLEConnection,不判断 deviceConnectType) */ connect(device: ScannedDevice): void { console.log(TAG, 'connect() 设备:', device.name) this._p.Blue.createBLEConnection(device.raw) } /** 主动断开当前连接 */ disconnect(): void { console.log(TAG, 'disconnect() 主动断开') this._stopKeepAlive() this._p.Blue.disconnect() } /** * 停止扫描(仅停止设备发现,不清连接态) * connectedDevice onHide / onUnload 时调用 */ stopScan(): void { console.log(TAG, 'stopScan() 停止扫描') this._stopReconnectTimer() this._busSubscribed = false wx.stopBluetoothDevicesDiscovery() this._onDevicesListCb = null this._onDeviceConnectCb = null } // ─── WiFi 配网 ──────────────────────────────── /** * 获取设备上报的 WiFi 列表(需设备已连接) * 内部延迟 2 秒后调用,15 秒超时 */ getWifiList(): Promise { console.log(TAG, 'getWifiList() 开始') return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接,无法获取 WiFi 列表')) return } const timer = setTimeout(() => { reject(new Error('[LeFu] 获取 WiFi 列表超时')) }, 15000) // 延迟 2 秒,等待设备就绪后再请求 setTimeout(() => { this._activeProtocol.dataFindSurroundDevice((res: any[]) => { clearTimeout(timer) console.log(TAG, 'getWifiList 原始结果:', res) if (!res || res.length === 0) { this._activeProtocol.dataExitWifiConfig(() => {}) resolve([]) return } const list: LeFuWifiItem[] = res.map((item: any) => ({ ssid: item.ssid || item.name || '', signal: item.signal, })) console.log(TAG, 'getWifiList 解析结果:', list.map(w => w.ssid)) resolve(list) }) }, 2000) }) } /** * 将 WiFi 配置写入设备 * @param ssid WiFi 名称 * @param password WiFi 密码 * @param version 协议版本:domain1(无鉴权)| domain2(有鉴权) * 返回码 23 = 配网成功 */ configWifi(ssid: string, password: string, version: WifiVersion): Promise { console.log(TAG, 'configWifi() ssid:', ssid, 'version:', version) return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接,无法配网')) return } const callback = (res: number) => { console.log(TAG, 'configWifi 回调 res:', res) if (res === 23) { console.log(TAG, 'configWifi 成功') resolve() } else { console.error(TAG, 'configWifi 失败,错误码:', res) reject(new Error(`[LeFu] 配网失败,错误码:${res}`)) } } if (version === 'domain1') { this._activeProtocol.dataConfigNetWork( { domain: LEFU_CONFIG.domain1, ssid, password }, callback, ) } else { this._activeProtocol.dataConfigUserNetWork( { domain: LEFU_CONFIG.domain2, ssid, password, userName: 'apiUser', userPassword: '3acebb95eb49577e9c2a2082589b9bd6', }, callback, ) } }) } /** * 查询设备是否已完成配网 * 返回 true = 已配网(0x01),false = 未配网 */ checkWifiConfig(): Promise { console.log(TAG, 'checkWifiConfig() 查询配网状态') return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接')) return } this._activeProtocol.codeFetchWifiConfig((res: number | undefined | null) => { console.log(TAG, 'checkWifiConfig 回调 res:', res, '已配网:', res === 0x01) resolve(res === 0x01) }) }) } // ─── OTA 升级 ───────────────────────────────── /** * 检查固件版本并按需触发 OTA 升级 * @returns true = 已触发升级,false = 无需升级 * 调用时机由业务层决定,不自动触发 */ checkAndUpgrade(): Promise { console.log(TAG, 'checkAndUpgrade() 开始检查固件版本') return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接')) return } if (!this._deviceInfo) { reject(new Error('[LeFu] 固件信息未就绪,请稍后重试')) return } const { firmwareRevision, modelNumber } = this._deviceInfo console.log(TAG, '当前固件:', firmwareRevision, '型号:', modelNumber) const [mcuVer, bleVer, wifiVer, resVer] = (firmwareRevision || '').split('.') // 设备单段版本(如 "006")vs 服务端 x.y.z 格式(如 "0.0.6")比对 const isServerNewer = (deviceVer: string, serverVer: string): boolean => { const deviceNum = parseInt(deviceVer, 10) const serverNum = (serverVer || '').split('.').map(v => parseInt(v, 10)).reduce((acc, v) => acc * 1000 + v, 0) return serverNum > deviceNum } get('weighingScale/v2/firmware/version', { type: modelNumber }, { loading: false }) .then(res => { const { mcuVersion, bleVersion, wifiVersion, resVersion } = res.result console.log(TAG, '服务端固件版本:', { mcuVersion, bleVersion, wifiVersion, resVersion }) const needUpdate = isServerNewer(mcuVer, mcuVersion) || isServerNewer(bleVer, bleVersion) || isServerNewer(wifiVer, wifiVersion) || isServerNewer(resVer, resVersion) console.log(TAG, '需要 OTA 升级:', needUpdate) if (!needUpdate) { resolve(false) return } // status: false = 成功,true = 失败 this._activeProtocol.codeOtaUpdate((status: boolean) => { console.log(TAG, 'OTA 升级回调 status:', status) if (!status) { resolve(true) } else { reject(new Error('[LeFu] OTA 升级失败')) } }) }) .catch(reject) }) } // ─── 私有:保活与重连 ───────────────────────── private _startKeepAlive(): void { this._stopKeepAlive() console.log(TAG, '_startKeepAlive 保活心跳启动') this._keepAliveTimer = setInterval(() => { this._activeProtocol?.sendKeepAliveCode?.() }, 15000) } private _stopKeepAlive(): void { if (this._keepAliveTimer !== null) { clearInterval(this._keepAliveTimer) this._keepAliveTimer = null console.log(TAG, '_stopKeepAlive 保活心跳停止') } } private _stopReconnectTimer(): void { if (this._reconnectTimer !== null) { clearTimeout(this._reconnectTimer) this._reconnectTimer = null } this._reconnectCount = 0 } /** 自动重连,最多 _maxReconnect 次,超限后等待用户手动重试 */ private _doReconnect(): void { if (this._reconnectCount >= this._maxReconnect) { console.warn(TAG, '重连次数已达上限:', this._maxReconnect) return } this._reconnectCount++ console.log(TAG, `_doReconnect 第 ${this._reconnectCount} 次重连`) this._stopReconnectTimer() this._reconnectTimer = setTimeout(() => { this._plugin?.Blue?.disconnect((res: any) => { console.log(TAG, '重连 disconnect 回调:', res?.errCode) if (res?.errCode === 0) { this._plugin?.Blue?.startBluetoothDevicesDiscovery() } }) }, 2000) } // ─── 成员同步 ───────────────────────────────── /** * 全量将成员列表同步到设备 * 流程:清除设备成员数据 → 逐个下发(顺序执行) * 每步 15 秒超时保护,失败时自动重试当前成员 * @param members 成员列表,必须包含且仅包含一个 isSelf=true 的主用户 */ syncMembersToDevice(members: DeviceMember[]): Promise { console.log(TAG, 'syncMembersToDevice() 成员数:', members.length) return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接,无法同步成员')) return } const mainUser = members.find(m => m.isSelf) if (!mainUser) { reject(new Error('[LeFu] 成员列表中缺少主用户(isSelf=true)')) return } // 第一步:清除设备上的所有成员数据("01" 仅清成员,保留历史) const clearTimer = setTimeout(() => { reject(new Error('[LeFu] 清除设备成员超时')) }, 15000) this._activeProtocol.codeClearDeviceData('01', (clearRes: number) => { clearTimeout(clearTimer) console.log(TAG, 'codeClearDeviceData 回调:', clearRes) if (clearRes !== 0x00) { reject(new Error('[LeFu] 清除设备成员失败')) return } // 第二步:逐个下发成员(顺序执行,失败自动重试当前) let index = 0 let syncTimer: number | null = null const resetTimer = () => { if (syncTimer !== null) clearTimeout(syncTimer) syncTimer = setTimeout(() => { reject(new Error(`[LeFu] 第 ${index + 1} 个成员下发超时`)) }, 15000) } const syncNext = () => { if (index >= members.length) { if (syncTimer !== null) clearTimeout(syncTimer) console.log(TAG, 'syncMembersToDevice 全部同步完成') resolve() return } const member = members[index] console.log(TAG, `下发第 ${index + 1}/${members.length} 个成员:`, member.name) resetTimer() this._activeProtocol.dataSyncUserInfo( { userID: mainUser.id, userName: member.name, // 主用户 memberID 为空,子用户用自己的 id memberID: member.isSelf ? '' : member.id, age: member.age, gender: member.gender, height: member.height, isAthleteMode: 0, deviceHeaderIndex: index, currentWeight: '', targetWeight: '', idealWeight: '', recentData: [], }, (res: number) => { console.log(TAG, `第 ${index + 1} 个成员同步回调:`, res) if (res === 0) { index++ } // 成功进下一个,失败重试当前(index 不变) syncNext() }, ) } syncNext() }) }) } } /** 全局单例,整个小程序共享同一个蓝牙服务实例 */ export const lefuService = new LeFuService()