import { get } from '../utils/request/index' import type { RawDevice, LeFuPlugin, ScannedDevice, LeFuWifiItem, ProgressData, LockData, LeFuConfig, DeviceSetting, DeviceMember, DeviceInfo, FirmwareVersion, } from './types' /** 顶层加载插件(对齐 demo,requirePlugin 必须在文件顶层调用) */ const plugin = requirePlugin('ppScale-plugin') as LeFuPlugin /** 乐福 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 _activeProtocol: any = null private _connectState = '' private _deviceInfo: DeviceInfo | null = null private _busSubscribed = false private _intentionalConnect = false private _reconnectCount = 0 private readonly _maxReconnect = 3 private _reconnectTimer: number | null = null private _keepAliveTimer: number | null = null 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 get config(): LeFuConfig { return LEFU_CONFIG } get isConnected(): boolean { return !!this._activeProtocol } get connectState(): string { return this._connectState } get BLUE_STATE(): Record { return plugin.BLUE_STATE ?? {} } get deviceInfo(): DeviceInfo | null { return this._deviceInfo } // ─── 回调注册 ───────────────────────────────── onDevicesList(cb: (devices: ScannedDevice[]) => void): void { this._onDevicesListCb = cb } onConnectState(cb: (state: string) => void): void { this._onConnectStateCb = cb } onDeviceConnect(cb: () => void): void { this._onDeviceConnectCb = cb } 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 } onDeviceInfo(cb: (info: DeviceInfo) => void): void { this._onDeviceInfoCb = cb } // ─── 扫描与连接 ─────────────────────────────── startScan(): void { console.log(TAG, 'startScan()') plugin.Blue.setDeviceSetting(DEVICE_SETTINGS) this._subscribeBus() plugin.Blue.start(DEVICE_SETTINGS.map(s => s.deviceName), false) } private _subscribeBus(): void { if (this._busSubscribed) { console.log(TAG, 'bus 已订阅,跳过') return } this._busSubscribed = true plugin.bus.subscribe('devicesList', (devList: RawDevice[]) => { console.log(TAG, 'bus[devicesList],设备数:', devList?.length, devList) if (!devList?.length) { this._onDevicesListCb?.([]) return } const devices: ScannedDevice[] = devList.map((item: RawDevice) => ({ name: item.name || item.deviceName || '未知设备', raw: item, model: {}, })) this._onDevicesListCb?.(devices) }) plugin.bus.subscribe('devicesModel', (res: any) => { console.log(TAG, 'bus[devicesModel] 完整数据:', JSON.stringify(res)) }) plugin.bus.subscribe('deviceInfo', (res: DeviceInfo) => { console.log(TAG, 'bus[deviceInfo]:', res) this._deviceInfo = res this._onDeviceInfoCb?.(res) }) plugin.bus.subscribe('deviceConnect', () => { console.log(TAG, 'bus[deviceConnect]') this._intentionalConnect = false this._reconnectCount = 0 plugin.ScaleAction.startDataProgress(true) this._activeProtocol = plugin.ScaleAction.getActiveProtocol() this._connectState = plugin.BLUE_STATE.CONNECTSUCCESS this._onDeviceConnectCb?.() }) plugin.bus.subscribe('connectState', (res: string) => { console.log(TAG, 'bus[connectState]:', res) this._connectState = res this._onConnectStateCb?.(res) if (res === plugin.BLUE_STATE.CONNECTSUCCESS) { this._startKeepAlive() } if (res === plugin.BLUE_STATE.CONNECTFAILED) { this._stopKeepAlive() if (!this._intentionalConnect) { this._doReconnect() } } }) plugin.bus.subscribe('progressData', (res: ProgressData) => { this._onProgressCb?.(res) }) plugin.bus.subscribe('lockData', (res: LockData) => { this._onLockedCb?.(res) }) plugin.bus.subscribe('deviceWillDisconnect', () => { console.warn(TAG, 'bus[deviceWillDisconnect],intentionalConnect:', this._intentionalConnect) this._stopKeepAlive() this._onDisconnectedCb?.() if (!this._intentionalConnect) { this._doReconnect() } }) } connect(rawDevice: RawDevice): void { console.log(TAG, 'connect():', rawDevice.name) this._intentionalConnect = true this._stopKeepAlive() this._stopReconnectTimer() this._activeProtocol = null this._connectState = '' this._deviceInfo = null this._onDeviceInfoCb = null this._onDeviceConnectCb = null // 直连缓存设备时不走 startScan,需手动初始化插件设备配置和总线订阅 plugin.Blue.setDeviceSetting(DEVICE_SETTINGS) this._subscribeBus() plugin.Blue.stopBluetoothDevicesDiscovery() // 0.0.25 新增:重新连接前必须清除协议缓存 plugin.Blue.clearProtocol() plugin.Blue.createBLEConnection(rawDevice) } disconnect(): void { console.log(TAG, 'disconnect()') this._stopKeepAlive() plugin.Blue.disconnect() } stopScan(): void { console.log(TAG, 'stopScan()') this._stopReconnectTimer() wx.stopBluetoothDevicesDiscovery() this._onDevicesListCb = null this._onDeviceConnectCb = null } // ─── WiFi 配网 ──────────────────────────────── 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) setTimeout(() => { this._activeProtocol.dataFindSurroundDevice((res: any[]) => { clearTimeout(timer) console.log(TAG, 'getWifiList 结果:', res) if (!res?.length) { this._activeProtocol.dataExitWifiConfig(() => {}) resolve([]) return } resolve(res.map((item: any) => ({ ssid: item.ssid || item.name || '', signal: item.signal }))) }) }, 2000) }) } configWifi(ssid: string, password: string): Promise { const version = (this._deviceInfo?.modelNumber ?? '').split('-').length === 5 ? 'domain2' : 'domain1' console.log(TAG, 'configWifi():', ssid, version) return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接,无法配网')) return } const callback = (res: number) => { console.log(TAG, 'configWifi 回调:', res) res === 23 ? resolve() : 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, ) } }) } checkWifiConfig(): Promise { return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接')); return } this._activeProtocol.codeFetchWifiConfig((res: number | undefined | null) => { resolve(res === 0x01) }) }) } // ─── OTA 升级 ───────────────────────────────── checkAndUpgrade(): Promise { 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 const [mcuVer, bleVer, wifiVer, resVer] = (firmwareRevision || '').split('.') const isServerNewer = (dv: string, sv: string) => { const dn = parseInt(dv, 10) const sn = (sv || '').split('.').map(v => parseInt(v, 10)).reduce((a, v) => a * 1000 + v, 0) return sn > dn } get('weighingScale/v2/firmware/version', { type: modelNumber }, { loading: false }) .then(res => { const { mcuVersion, bleVersion, wifiVersion, resVersion } = res.result const needUpdate = isServerNewer(mcuVer, mcuVersion) || isServerNewer(bleVer, bleVersion) || isServerNewer(wifiVer, wifiVersion) || isServerNewer(resVer, resVersion) if (!needUpdate) { resolve(false); return } this._activeProtocol.codeOtaUpdate((status: boolean) => { status ? reject(new Error('[LeFu] OTA 升级失败')) : resolve(true) }) }) .catch(reject) }) } // ─── 私有:保活与重连 ───────────────────────── private _startKeepAlive(): void { this._stopKeepAlive() this._keepAliveTimer = setInterval(() => { this._activeProtocol?.sendKeepAliveCode?.() }, 15000) } private _stopKeepAlive(): void { if (this._keepAliveTimer !== null) { clearInterval(this._keepAliveTimer) this._keepAliveTimer = null } } private _stopReconnectTimer(): void { if (this._reconnectTimer !== null) { clearTimeout(this._reconnectTimer) this._reconnectTimer = null } this._reconnectCount = 0 } private _doReconnect(): void { if (this._reconnectCount >= this._maxReconnect) { console.warn(TAG, '重连次数已达上限') return } this._reconnectCount++ console.log(TAG, `_doReconnect 第 ${this._reconnectCount} 次`) this._stopReconnectTimer() this._reconnectTimer = setTimeout(() => { plugin.Blue.disconnect((res: any) => { if (res?.errCode === 0) plugin.Blue.startBluetoothDevicesDiscovery() }) }, 2000) } // ─── 成员同步 ───────────────────────────────── syncMembersToDevice(members: DeviceMember[]): Promise { 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] 缺少主用户')); return } const clearTimer = setTimeout(() => reject(new Error('[LeFu] 清除设备成员超时')), 15000) this._activeProtocol.codeClearDeviceData('01', (clearRes: number) => { clearTimeout(clearTimer) 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) resolve() return } const member = members[index] resetTimer() this._activeProtocol.dataSyncUserInfo( { userID: mainUser.id, userName: member.name, memberID: member.isSelf ? '' : member.id, age: member.age, gender: member.gender, height: member.height, isAthleteMode: 0, deviceHeaderIndex: index, currentWeight: '', targetWeight: '', idealWeight: '', recentData: [], }, (res: number) => { if (res === 0) index++; syncNext() }, ) } syncNext() }) }) } } export const lefuService = new LeFuService()