[AI Generated]: fix(*): 修复乐福SDK二次连接问题,对齐demo挂载顺序并完善页面状态重置

This commit is contained in:
17792275749
2026-05-14 19:56:00 +08:00
parent 00a1529256
commit 5c8370b943
9 changed files with 171 additions and 471 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ClaudeCodeTabState"> <component name="ClaudeCodeTabState">
<option name="tabCount" value="2" /> <option name="tabCount" value="4" />
</component> </component>
</project> </project>
-4
View File
@@ -1,5 +1,3 @@
import { lefuService } from './lefu/index'
App<IAppOption>({ App<IAppOption>({
globalData: { globalData: {
/** 当前已连接设备的蓝牙状态,页面可读取 */ /** 当前已连接设备的蓝牙状态,页面可读取 */
@@ -7,8 +5,6 @@ App<IAppOption>({
}, },
onLaunch() { onLaunch() {
// 初始化乐福蓝牙 SDK
lefuService.init()
// 已登录则跳过登录页 // 已登录则跳过登录页
// const userInfo = wx.getStorageSync('userInfo') // const userInfo = wx.getStorageSync('userInfo')
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 121 KiB

+94 -320
View File
@@ -14,14 +14,17 @@ import type {
FirmwareVersion, FirmwareVersion,
} from './types' } from './types'
/** 顶层加载插件(对齐 demorequirePlugin 必须在文件顶层调用) */
const plugin = requirePlugin('ppScale-plugin') as LeFuPlugin
/** 乐福 SDK 配置 */ /** 乐福 SDK 配置 */
const LEFU_CONFIG: LeFuConfig = { const LEFU_CONFIG: LeFuConfig = {
key: 'lefudc91611833e18d94', key: 'lefudc91611833e18d94',
secret: 'eQ50JYimMp0X7uhNLj6D3lTEk4TUUvEG7dvaqKM9t1U=', secret: 'eQ50JYimMp0X7uhNLj6D3lTEk4TUUvEG7dvaqKM9t1U=',
domain1: 'http://192.168.1.31:8889', // 测试环境(老版本 无鉴权) domain1: 'http://192.168.1.31:8889',
domain2: 'http://192.168.1.31:8889/weight', // 测试环境(新版本 有鉴权) domain2: 'http://192.168.1.31:8889/weight',
// domain1: 'http://device.shuziweidao.com:80/gateway', // 正式环境 // domain1: 'http://device.shuziweidao.com:80/gateway',
// domain2: 'http://device.shuziweidao.com:80/weight', // 正式环境 // domain2: 'http://device.shuziweidao.com:80/weight',
} }
/** 支持的设备型号配置列表(5款) */ /** 支持的设备型号配置列表(5款) */
@@ -71,25 +74,17 @@ const DEVICE_SETTINGS: DeviceSetting[] = [
const TAG = '[LeFuService]' const TAG = '[LeFuService]'
class LeFuService { class LeFuService {
private _plugin: LeFuPlugin | null = null
/** 设备协议对象,devicesModel 事件返回,用于 WiFi 配网等高级操作 */
private _activeProtocol: any = null private _activeProtocol: any = null
private _connectState = ''
private _deviceInfo: DeviceInfo | null = null
private _busSubscribed = false
private _intentionalConnect = false
/** 重连次数 */
private _reconnectCount = 0 private _reconnectCount = 0
private readonly _maxReconnect = 3 private readonly _maxReconnect = 3
private _reconnectTimer: number | null = null private _reconnectTimer: number | null = null
/** 保活心跳定时器 */
private _keepAliveTimer: 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 _onDevicesListCb: ((devices: ScannedDevice[]) => void) | null = null
private _onConnectStateCb: ((state: string) => void) | null = null private _onConnectStateCb: ((state: string) => void) | null = null
private _onDeviceConnectCb: (() => void) | null = null private _onDeviceConnectCb: (() => void) | null = null
@@ -98,142 +93,63 @@ class LeFuService {
private _onDisconnectedCb: (() => void) | null = null private _onDisconnectedCb: (() => void) | null = null
private _onDeviceInfoCb: ((info: DeviceInfo) => void) | null = null private _onDeviceInfoCb: ((info: DeviceInfo) => void) | null = null
/** 在 app.ts onLaunch 中调用,加载 ppScale-plugin */ get config(): LeFuConfig { return LEFU_CONFIG }
init(): void { get isConnected(): boolean { return !!this._activeProtocol }
console.log(TAG, 'init() 开始加载插件') get connectState(): string { return this._connectState }
this._plugin = requirePlugin('ppScale-plugin') as LeFuPlugin get BLUE_STATE(): Record<string, string> { return plugin.BLUE_STATE ?? {} }
console.log(TAG, 'init() 插件加载完成:', !!this._plugin) get deviceInfo(): DeviceInfo | null { return this._deviceInfo }
}
/** 获取插件(未初始化时抛出) */
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 { this._onDevicesListCb = cb }
/** 注册设备列表回调(扫描到设备时触发) */ onConnectState(cb: (state: string) => void): void { this._onConnectStateCb = cb }
onDevicesList(cb: (devices: ScannedDevice[]) => void): void { onDeviceConnect(cb: () => void): void { this._onDeviceConnectCb = cb }
console.log(TAG, 'onDevicesList 回调已注册') onMeasuring(cb: (data: ProgressData) => void): void { this._onProgressCb = cb }
this._onDevicesListCb = 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 }
/** 注册蓝牙连接状态回调 */
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<string, string> {
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,再 subscribemaster searchDevice.js 顺序)
*/
startScan(): void { startScan(): void {
console.log(TAG, 'startScan() 开始,busSubscribed:', this._busSubscribed) console.log(TAG, 'startScan()')
const plugin = this._p
// ① 先设置设备配置并启动扫描(必须在 subscribe 之前,与 master 一致)
plugin.Blue.setDeviceSetting(DEVICE_SETTINGS) plugin.Blue.setDeviceSetting(DEVICE_SETTINGS)
const deviceNames = DEVICE_SETTINGS.map(s => s.deviceName) this._subscribeBus()
console.log(TAG, 'plugin.Blue.start() 调用,设备名称:', deviceNames) plugin.Blue.start(DEVICE_SETTINGS.map(s => s.deviceName), false)
plugin.Blue.start(deviceNames, false) }
// bus 订阅只注册一次,stopScan 时重置,避免多次搜索导致订阅累积 private _subscribeBus(): void {
if (this._busSubscribed) { if (this._busSubscribed) {
console.log(TAG, 'bus 已订阅,跳过重复注册') console.log(TAG, 'bus 已订阅,跳过')
return return
} }
this._busSubscribed = true this._busSubscribed = true
// ② 启动后再订阅设备列表(master 也是 start 后才 subscribe
plugin.bus.subscribe('devicesList', (devList: RawDevice[]) => { plugin.bus.subscribe('devicesList', (devList: RawDevice[]) => {
console.log(TAG, 'bus[devicesList] 触发,原始设备数:', devList?.length, devList?.map((d: RawDevice) => d.name || d.deviceName)) console.log(TAG, 'bus[devicesList]设备数:', devList?.length)
if (!devList || !devList.length) { if (!devList?.length) {
this._onDevicesListCb?.([]) this._onDevicesListCb?.([])
return return
} }
// 直接用原始列表转换,不调用 getDeviceModelmaster 也是直接传 res
const devices: ScannedDevice[] = devList.map((item: RawDevice) => ({ const devices: ScannedDevice[] = devList.map((item: RawDevice) => ({
name: item.name || item.deviceName || '未知设备', name: item.name || item.deviceName || '未知设备',
raw: item, raw: item,
model: {}, model: {},
})) }))
console.log(TAG, '转换后设备列表:', devices.map(d => d.name))
this._onDevicesListCb?.(devices) this._onDevicesListCb?.(devices)
}) })
// 设备模型信息(含 mac 等,仅用于日志/调试)
plugin.bus.subscribe('devicesModel', (res: any) => { plugin.bus.subscribe('devicesModel', (res: any) => {
console.log(TAG, 'bus[devicesModel] 触发,deviceMac:', res?.deviceMac) console.log(TAG, 'bus[devicesModel] 完整数据:', JSON.stringify(res))
}) })
// 连接成功后获取固件信息(用于后续 OTA 版本比对)
plugin.bus.subscribe('deviceInfo', (res: DeviceInfo) => { plugin.bus.subscribe('deviceInfo', (res: DeviceInfo) => {
console.log(TAG, 'bus[deviceInfo] 触发:',res, res?.firmwareRevision, res?.modelNumber) console.log(TAG, 'bus[deviceInfo]:', res?.modelNumber)
this._deviceInfo = res this._deviceInfo = res
this._onDeviceInfoCb?.(res) this._onDeviceInfoCb?.(res)
}) })
// 连接建立:从 ScaleAction 获取 activeProtocol,启动数据进度(对齐 master configureDevice.js
plugin.bus.subscribe('deviceConnect', () => { plugin.bus.subscribe('deviceConnect', () => {
console.log(TAG, 'bus[deviceConnect] 触发,获取 activeProtocol,启动数据进度') console.log(TAG, 'bus[deviceConnect]')
this._intentionalConnect = false
this._reconnectCount = 0 this._reconnectCount = 0
plugin.ScaleAction.startDataProgress(true) plugin.ScaleAction.startDataProgress(true)
this._activeProtocol = plugin.ScaleAction.getActiveProtocol() this._activeProtocol = plugin.ScaleAction.getActiveProtocol()
@@ -241,228 +157,144 @@ class LeFuService {
this._onDeviceConnectCb?.() this._onDeviceConnectCb?.()
}) })
// 连接状态变化:CONNECTSUCCESS 时启动保活,CONNECTFAILED 时重连
plugin.bus.subscribe('connectState', (res: string) => { plugin.bus.subscribe('connectState', (res: string) => {
console.log(TAG, 'bus[connectState] 触发:', res) console.log(TAG, 'bus[connectState]:', res)
this._connectState = res this._connectState = res
this._onConnectStateCb?.(res) this._onConnectStateCb?.(res)
if (res === plugin.BLUE_STATE.CONNECTSUCCESS) { if (res === plugin.BLUE_STATE.CONNECTSUCCESS) {
console.log(TAG, '连接成功,启动保活心跳')
this._startKeepAlive() this._startKeepAlive()
} }
if (res === plugin.BLUE_STATE.CONNECTFAILED) { if (res === plugin.BLUE_STATE.CONNECTFAILED) {
console.warn(TAG, '连接失败,触发重连逻辑')
this._stopKeepAlive() this._stopKeepAlive()
if (!this._intentionalConnect) {
this._doReconnect() this._doReconnect()
} }
}
}) })
// 实时测量数据
plugin.bus.subscribe('progressData', (res: ProgressData) => { plugin.bus.subscribe('progressData', (res: ProgressData) => {
console.log(TAG, 'bus[progressData] 体重(g):', res?.weight)
this._onProgressCb?.(res) this._onProgressCb?.(res)
}) })
// 测量锁定(完成)
plugin.bus.subscribe('lockData', (res: LockData) => { plugin.bus.subscribe('lockData', (res: LockData) => {
console.log(TAG, 'bus[lockData] 测量完成,体重(g):', res?.weight, '阻抗:', res?.resistance)
this._onLockedCb?.(res) this._onLockedCb?.(res)
}) })
// 设备自动断开
plugin.bus.subscribe('deviceWillDisconnect', () => { plugin.bus.subscribe('deviceWillDisconnect', () => {
console.warn(TAG, 'bus[deviceWillDisconnect] 设备断开,触发重连') console.warn(TAG, 'bus[deviceWillDisconnect]intentionalConnect:', this._intentionalConnect)
this._stopKeepAlive() this._stopKeepAlive()
this._onDisconnectedCb?.() this._onDisconnectedCb?.()
if (!this._intentionalConnect) {
this._doReconnect() this._doReconnect()
}
}) })
} }
/**
* 主动连接指定设备(对齐 master:直接调 createBLEConnection,不判断 deviceConnectType
*/
connect(device: ScannedDevice): void { connect(device: ScannedDevice): void {
console.log(TAG, 'connect() 设备:', device.name) console.log(TAG, 'connect():', device.name)
this._p.Blue.createBLEConnection(device.raw) this._intentionalConnect = true
}
/** 主动断开当前连接 */
disconnect(): void {
console.log(TAG, 'disconnect() 主动断开')
this._stopKeepAlive() this._stopKeepAlive()
this._p.Blue.disconnect() this._stopReconnectTimer()
this._activeProtocol = null
this._connectState = ''
this._deviceInfo = null
this._onDeviceInfoCb = null
this._onDeviceConnectCb = null
plugin.Blue.stopBluetoothDevicesDiscovery()
plugin.Blue.createBLEConnection(device.raw)
}
disconnect(): void {
console.log(TAG, 'disconnect()')
this._stopKeepAlive()
plugin.Blue.disconnect()
} }
/**
* 停止扫描(仅停止设备发现,不清连接态)
* connectedDevice onHide / onUnload 时调用
*/
stopScan(): void { stopScan(): void {
console.log(TAG, 'stopScan() 停止扫描') console.log(TAG, 'stopScan()')
this._stopReconnectTimer() this._stopReconnectTimer()
this._busSubscribed = false
wx.stopBluetoothDevicesDiscovery() wx.stopBluetoothDevicesDiscovery()
this._onDevicesListCb = null this._onDevicesListCb = null
this._onDeviceConnectCb = null this._onDeviceConnectCb = null
} }
// ─── WiFi 配网 ──────────────────────────────── // ─── WiFi 配网 ────────────────────────────────
/**
* 获取设备上报的 WiFi 列表(需设备已连接)
* 内部延迟 2 秒后调用,15 秒超时
*/
getWifiList(): Promise<LeFuWifiItem[]> { getWifiList(): Promise<LeFuWifiItem[]> {
console.log(TAG, 'getWifiList() 开始') console.log(TAG, 'getWifiList()')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this._activeProtocol) { if (!this._activeProtocol) {
reject(new Error('[LeFu] 设备未连接,无法获取 WiFi 列表')) reject(new Error('[LeFu] 设备未连接,无法获取 WiFi 列表'))
return return
} }
const timer = setTimeout(() => reject(new Error('[LeFu] 获取 WiFi 列表超时')), 15000)
const timer = setTimeout(() => {
reject(new Error('[LeFu] 获取 WiFi 列表超时'))
}, 15000)
// 延迟 2 秒,等待设备就绪后再请求
setTimeout(() => { setTimeout(() => {
this._activeProtocol.dataFindSurroundDevice((res: any[]) => { this._activeProtocol.dataFindSurroundDevice((res: any[]) => {
clearTimeout(timer) clearTimeout(timer)
console.log(TAG, 'getWifiList 原始结果:', res) console.log(TAG, 'getWifiList 结果:', res)
if (!res || res.length === 0) { if (!res?.length) {
this._activeProtocol.dataExitWifiConfig(() => {}) this._activeProtocol.dataExitWifiConfig(() => {})
resolve([]) resolve([])
return return
} }
const list: LeFuWifiItem[] = res.map((item: any) => ({ resolve(res.map((item: any) => ({ ssid: item.ssid || item.name || '', signal: item.signal })))
ssid: item.ssid || item.name || '',
signal: item.signal,
}))
console.log(TAG, 'getWifiList 解析结果:', list.map(w => w.ssid))
resolve(list)
}) })
}, 2000) }, 2000)
}) })
} }
/**
* 将 WiFi 配置写入设备
* @param ssid WiFi 名称
* @param password WiFi 密码
* @param version 协议版本:domain1(无鉴权)| domain2(有鉴权)
* 返回码 23 = 配网成功
*/
configWifi(ssid: string, password: string, version: WifiVersion): Promise<void> { configWifi(ssid: string, password: string, version: WifiVersion): Promise<void> {
console.log(TAG, 'configWifi() ssid:', ssid, 'version:', version) console.log(TAG, 'configWifi():', ssid, version)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this._activeProtocol) { if (!this._activeProtocol) {
reject(new Error('[LeFu] 设备未连接,无法配网')) reject(new Error('[LeFu] 设备未连接,无法配网'))
return return
} }
const callback = (res: number) => { const callback = (res: number) => {
console.log(TAG, 'configWifi 回调 res:', res) console.log(TAG, 'configWifi 回调:', res)
if (res === 23) { res === 23 ? resolve() : reject(new Error(`[LeFu] 配网失败,错误码:${res}`))
console.log(TAG, 'configWifi 成功')
resolve()
} else {
console.error(TAG, 'configWifi 失败,错误码:', res)
reject(new Error(`[LeFu] 配网失败,错误码:${res}`))
} }
}
if (version === 'domain1') { if (version === 'domain1') {
this._activeProtocol.dataConfigNetWork( this._activeProtocol.dataConfigNetWork({ domain: LEFU_CONFIG.domain1, ssid, password }, callback)
{ domain: LEFU_CONFIG.domain1, ssid, password },
callback,
)
} else { } else {
this._activeProtocol.dataConfigUserNetWork( this._activeProtocol.dataConfigUserNetWork(
{ { domain: LEFU_CONFIG.domain2, ssid, password, userName: 'apiUser', userPassword: '3acebb95eb49577e9c2a2082589b9bd6' },
domain: LEFU_CONFIG.domain2,
ssid,
password,
userName: 'apiUser',
userPassword: '3acebb95eb49577e9c2a2082589b9bd6',
},
callback, callback,
) )
} }
}) })
} }
/**
* 查询设备是否已完成配网
* 返回 true = 已配网(0x01),false = 未配网
*/
checkWifiConfig(): Promise<boolean> { checkWifiConfig(): Promise<boolean> {
console.log(TAG, 'checkWifiConfig() 查询配网状态')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this._activeProtocol) { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接')); return }
reject(new Error('[LeFu] 设备未连接'))
return
}
this._activeProtocol.codeFetchWifiConfig((res: number | undefined | null) => { this._activeProtocol.codeFetchWifiConfig((res: number | undefined | null) => {
console.log(TAG, 'checkWifiConfig 回调 res:', res, '已配网:', res === 0x01)
resolve(res === 0x01) resolve(res === 0x01)
}) })
}) })
} }
// ─── OTA 升级 ───────────────────────────────── // ─── OTA 升级 ─────────────────────────────────
/**
* 检查固件版本并按需触发 OTA 升级
* @returns true = 已触发升级,false = 无需升级
* 调用时机由业务层决定,不自动触发
*/
checkAndUpgrade(): Promise<boolean> { checkAndUpgrade(): Promise<boolean> {
console.log(TAG, 'checkAndUpgrade() 开始检查固件版本')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this._activeProtocol) { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接')); return }
reject(new Error('[LeFu] 设备未连接')) if (!this._deviceInfo) { reject(new Error('[LeFu] 固件信息未就绪')); return }
return
}
if (!this._deviceInfo) {
reject(new Error('[LeFu] 固件信息未就绪,请稍后重试'))
return
}
const { firmwareRevision, modelNumber } = this._deviceInfo const { firmwareRevision, modelNumber } = this._deviceInfo
console.log(TAG, '当前固件:', firmwareRevision, '型号:', modelNumber)
const [mcuVer, bleVer, wifiVer, resVer] = (firmwareRevision || '').split('.') const [mcuVer, bleVer, wifiVer, resVer] = (firmwareRevision || '').split('.')
const isServerNewer = (dv: string, sv: string) => {
// 设备单段版本(如 "006"vs 服务端 x.y.z 格式(如 "0.0.6")比对 const dn = parseInt(dv, 10)
const isServerNewer = (deviceVer: string, serverVer: string): boolean => { const sn = (sv || '').split('.').map(v => parseInt(v, 10)).reduce((a, v) => a * 1000 + v, 0)
const deviceNum = parseInt(deviceVer, 10) return sn > dn
const serverNum = (serverVer || '').split('.').map(v => parseInt(v, 10)).reduce((acc, v) => acc * 1000 + v, 0)
return serverNum > deviceNum
} }
get<FirmwareVersion>('weighingScale/v2/firmware/version', { type: modelNumber }, { loading: false }) get<FirmwareVersion>('weighingScale/v2/firmware/version', { type: modelNumber }, { loading: false })
.then(res => { .then(res => {
const { mcuVersion, bleVersion, wifiVersion, resVersion } = res.result const { mcuVersion, bleVersion, wifiVersion, resVersion } = res.result
console.log(TAG, '服务端固件版本:', { mcuVersion, bleVersion, wifiVersion, resVersion }) const needUpdate = isServerNewer(mcuVer, mcuVersion) || isServerNewer(bleVer, bleVersion)
const needUpdate = || isServerNewer(wifiVer, wifiVersion) || isServerNewer(resVer, resVersion)
isServerNewer(mcuVer, mcuVersion) || if (!needUpdate) { resolve(false); return }
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) => { this._activeProtocol.codeOtaUpdate((status: boolean) => {
console.log(TAG, 'OTA 升级回调 status:', status) status ? reject(new Error('[LeFu] OTA 升级失败')) : resolve(true)
if (!status) {
resolve(true)
} else {
reject(new Error('[LeFu] OTA 升级失败'))
}
}) })
}) })
.catch(reject) .catch(reject)
@@ -470,20 +302,15 @@ class LeFuService {
} }
// ─── 私有:保活与重连 ───────────────────────── // ─── 私有:保活与重连 ─────────────────────────
private _startKeepAlive(): void { private _startKeepAlive(): void {
this._stopKeepAlive() this._stopKeepAlive()
console.log(TAG, '_startKeepAlive 保活心跳启动') this._keepAliveTimer = setInterval(() => { this._activeProtocol?.sendKeepAliveCode?.() }, 15000)
this._keepAliveTimer = setInterval(() => {
this._activeProtocol?.sendKeepAliveCode?.()
}, 15000)
} }
private _stopKeepAlive(): void { private _stopKeepAlive(): void {
if (this._keepAliveTimer !== null) { if (this._keepAliveTimer !== null) {
clearInterval(this._keepAliveTimer) clearInterval(this._keepAliveTimer)
this._keepAliveTimer = null this._keepAliveTimer = null
console.log(TAG, '_stopKeepAlive 保活心跳停止')
} }
} }
@@ -495,115 +322,62 @@ class LeFuService {
this._reconnectCount = 0 this._reconnectCount = 0
} }
/** 自动重连,最多 _maxReconnect 次,超限后等待用户手动重试 */
private _doReconnect(): void { private _doReconnect(): void {
if (this._reconnectCount >= this._maxReconnect) { if (this._reconnectCount >= this._maxReconnect) {
console.warn(TAG, '重连次数已达上限:', this._maxReconnect) console.warn(TAG, '重连次数已达上限')
return return
} }
this._reconnectCount++ this._reconnectCount++
console.log(TAG, `_doReconnect 第 ${this._reconnectCount}重连`) console.log(TAG, `_doReconnect 第 ${this._reconnectCount}`)
this._stopReconnectTimer() this._stopReconnectTimer()
this._reconnectTimer = setTimeout(() => { this._reconnectTimer = setTimeout(() => {
this._plugin?.Blue?.disconnect((res: any) => { plugin.Blue.disconnect((res: any) => {
console.log(TAG, '重连 disconnect 回调:', res?.errCode) if (res?.errCode === 0) plugin.Blue.startBluetoothDevicesDiscovery()
if (res?.errCode === 0) {
this._plugin?.Blue?.startBluetoothDevicesDiscovery()
}
}) })
}, 2000) }, 2000)
} }
// ─── 成员同步 ───────────────────────────────── // ─── 成员同步 ─────────────────────────────────
/**
* 全量将成员列表同步到设备
* 流程:清除设备成员数据 → 逐个下发(顺序执行)
* 每步 15 秒超时保护,失败时自动重试当前成员
* @param members 成员列表,必须包含且仅包含一个 isSelf=true 的主用户
*/
syncMembersToDevice(members: DeviceMember[]): Promise<void> { syncMembersToDevice(members: DeviceMember[]): Promise<void> {
console.log(TAG, 'syncMembersToDevice() 成员数:', members.length)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this._activeProtocol) { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接')); return }
reject(new Error('[LeFu] 设备未连接,无法同步成员'))
return
}
const mainUser = members.find(m => m.isSelf) const mainUser = members.find(m => m.isSelf)
if (!mainUser) { if (!mainUser) { reject(new Error('[LeFu] 缺少主用户')); return }
reject(new Error('[LeFu] 成员列表中缺少主用户(isSelf=true'))
return
}
// 第一步:清除设备上的所有成员数据("01" 仅清成员,保留历史)
const clearTimer = setTimeout(() => {
reject(new Error('[LeFu] 清除设备成员超时'))
}, 15000)
const clearTimer = setTimeout(() => reject(new Error('[LeFu] 清除设备成员超时')), 15000)
this._activeProtocol.codeClearDeviceData('01', (clearRes: number) => { this._activeProtocol.codeClearDeviceData('01', (clearRes: number) => {
clearTimeout(clearTimer) clearTimeout(clearTimer)
console.log(TAG, 'codeClearDeviceData 回调:', clearRes) if (clearRes !== 0x00) { reject(new Error('[LeFu] 清除设备成员失败')); return }
if (clearRes !== 0x00) {
reject(new Error('[LeFu] 清除设备成员失败'))
return
}
// 第二步:逐个下发成员(顺序执行,失败自动重试当前)
let index = 0 let index = 0
let syncTimer: number | null = null let syncTimer: number | null = null
const resetTimer = () => { const resetTimer = () => {
if (syncTimer !== null) clearTimeout(syncTimer) if (syncTimer !== null) clearTimeout(syncTimer)
syncTimer = setTimeout(() => { syncTimer = setTimeout(() => reject(new Error(`[LeFu] 第 ${index + 1} 个成员下发超时`)), 15000)
reject(new Error(`[LeFu] 第 ${index + 1} 个成员下发超时`))
}, 15000)
} }
const syncNext = () => { const syncNext = () => {
if (index >= members.length) { if (index >= members.length) {
if (syncTimer !== null) clearTimeout(syncTimer) if (syncTimer !== null) clearTimeout(syncTimer)
console.log(TAG, 'syncMembersToDevice 全部同步完成')
resolve() resolve()
return return
} }
const member = members[index] const member = members[index]
console.log(TAG, `下发第 ${index + 1}/${members.length} 个成员:`, member.name)
resetTimer() resetTimer()
this._activeProtocol.dataSyncUserInfo( this._activeProtocol.dataSyncUserInfo(
{ {
userID: mainUser.id, userID: mainUser.id, userName: member.name,
userName: member.name,
// 主用户 memberID 为空,子用户用自己的 id
memberID: member.isSelf ? '' : member.id, memberID: member.isSelf ? '' : member.id,
age: member.age, age: member.age, gender: member.gender, height: member.height,
gender: member.gender, isAthleteMode: 0, deviceHeaderIndex: index,
height: member.height, currentWeight: '', targetWeight: '', idealWeight: '', recentData: [],
isAthleteMode: 0,
deviceHeaderIndex: index,
currentWeight: '',
targetWeight: '',
idealWeight: '',
recentData: [],
},
(res: number) => {
console.log(TAG, `${index + 1} 个成员同步回调:`, res)
if (res === 0) {
index++
}
// 成功进下一个,失败重试当前(index 不变)
syncNext()
}, },
(res: number) => { if (res === 0) index++; syncNext() },
) )
} }
syncNext() syncNext()
}) })
}) })
} }
} }
/** 全局单例,整个小程序共享同一个蓝牙服务实例 */
export const lefuService = new LeFuService() export const lefuService = new LeFuService()
@@ -29,6 +29,8 @@ Page({
onHide() { onHide() {
console.log(TAG, 'onHide, 停止扫描') console.log(TAG, 'onHide, 停止扫描')
_rawDevices = []
this.setData({ status: 'idle', deviceList: [] })
lefuService.stopScan() lefuService.stopScan()
}, },
@@ -1,75 +1,29 @@
import { lefuService } from '../../lefu/index' import { lefuService } from '../../lefu/index'
import type { LeFuWifiItem, WifiVersion } from '../../lefu/index' import type { LeFuWifiItem, WifiVersion } from '../../lefu/index'
/** WiFi 展示项 */
interface WifiItem { interface WifiItem {
ssid: string ssid: string
/** 是否已通过本页配网成功 */
connected: boolean connected: boolean
} }
/** 当前已连接设备(顶部卡片) */
interface CurrentDevice {
id: string
name: string
}
/** 密码态选中的 WiFi */
interface SelectedWifi {
ssid: string
}
/** 页面状态 */
type WifiStatus = 'idle' | 'searching' | 'list' | 'empty' | 'password' | 'configuring' type WifiStatus = 'idle' | 'searching' | 'list' | 'empty' | 'password' | 'configuring'
type BleStatus = 'connected' | 'failed' | 'connecting'
Page({ Page({
/** 配网协议版本,两种都支持,默认 domain2(有鉴权) */
_wifiVersion: 'domain2' as WifiVersion, _wifiVersion: 'domain2' as WifiVersion,
data: { data: {
/**
* 页面状态
* - idle: 未搜索
* - searching: 正在从设备获取 WiFi 列表
* - list: 列表已就绪
* - empty: 设备未扫描到任何 WiFi
* - password: 选中某项,正在输入密码
* - configuring: 正在将 WiFi 写入设备
*/
status: 'idle' as WifiStatus, status: 'idle' as WifiStatus,
deviceName: '智能体重秤',
/** 顶部固定卡片 */ bleStatus: 'connected' as BleStatus,
currentDevice: {
id: '',
name: '智能体重秤',
} as CurrentDevice,
/** 蓝牙连接状态常量(从插件获取,供 wxml 做条件判断) */
BLUE_STATE: {} as Record<string, string>,
/** 当前蓝牙连接状态(实时更新) */
connectState: '',
/** 顶部卡片蓝牙状态:connected / failed / connecting */
bleStatus: 'connected' as 'connected' | 'failed' | 'connecting',
/** WiFi 列表 */
wifiList: [] as WifiItem[], wifiList: [] as WifiItem[],
/** 是否已有任一 WiFi 配网成功 */
hasConnected: false, hasConnected: false,
selectedSsid: '',
/** 当前密码态选中的 WiFi */
selectedWifi: { ssid: '' } as SelectedWifi,
/** 密码输入框值 */
selectWifiPWD: '', selectWifiPWD: '',
/** 密码框明文/密文切换:true 为密文(默认) */
type: true, type: true,
}, },
_toBleStatus(state: string): 'connected' | 'failed' | 'connecting' { _toBleStatus(state: string): BleStatus {
const bs = lefuService.BLUE_STATE const bs = lefuService.BLUE_STATE
if (state === bs.CONNECTSUCCESS || state === bs.WIFISUCCESS) return 'connected' if (state === bs.CONNECTSUCCESS || state === bs.WIFISUCCESS) return 'connected'
if (state === bs.CONNECTFAILED) return 'failed' if (state === bs.CONNECTFAILED) return 'failed'
@@ -77,9 +31,13 @@ Page({
}, },
onLoad() { onLoad() {
// 尽早注册 deviceInfo 回调,防止 onShow 前已触发而错过
lefuService.onDeviceInfo((info) => {
this.setData({ deviceName: info.modelNumber })
})
this.setData({ this.setData({
bleStatus: this._toBleStatus(lefuService.connectState), bleStatus: this._toBleStatus(lefuService.connectState),
currentDevice: { id: '', name: lefuService.deviceInfo?.modelNumber ?? '智能体重秤' }, deviceName: lefuService.deviceInfo?.modelNumber ?? '智能体重秤',
}) })
this.onStartSearch() this.onStartSearch()
}, },
@@ -87,14 +45,10 @@ Page({
onShow() { onShow() {
lefuService.onConnectState((state) => { lefuService.onConnectState((state) => {
const bs = lefuService.BLUE_STATE const bs = lefuService.BLUE_STATE
// 只处理明确的连接/断开状态,忽略蓝牙初始化过程中的中间状态
if (state === bs.CONNECTSUCCESS || state === bs.WIFISUCCESS || state === bs.CONNECTFAILED) { if (state === bs.CONNECTSUCCESS || state === bs.WIFISUCCESS || state === bs.CONNECTFAILED) {
this.setData({ bleStatus: this._toBleStatus(state) }) this.setData({ bleStatus: this._toBleStatus(state) })
} }
}) })
lefuService.onDeviceInfo((info) => {
this.setData({ currentDevice: { id: '', name: info.modelNumber } })
})
}, },
onUnload() { onUnload() {
@@ -104,11 +58,9 @@ Page({
} }
}, },
/** 从设备获取 WiFi 列表 */
onStartSearch() { onStartSearch() {
this.setData({ status: 'searching', wifiList: [] }) this.setData({ status: 'searching', wifiList: [] })
wx.showLoading({ title: '正在获取 WiFi 列表...', mask: true }) wx.showLoading({ title: '正在获取 WiFi 列表...', mask: true })
lefuService.getWifiList() lefuService.getWifiList()
.then((items: LeFuWifiItem[]) => { .then((items: LeFuWifiItem[]) => {
wx.hideLoading() wx.hideLoading()
@@ -116,11 +68,10 @@ Page({
this.setData({ status: 'empty' }) this.setData({ status: 'empty' })
return return
} }
const list: WifiItem[] = items.map(item => ({ this.setData({
ssid: item.ssid, status: 'list',
connected: false, wifiList: items.map(item => ({ ssid: item.ssid, connected: false })),
})) })
this.setData({ status: 'list', wifiList: list })
}) })
.catch(() => { .catch(() => {
wx.hideLoading() wx.hideLoading()
@@ -128,64 +79,42 @@ Page({
}) })
}, },
/** 空态刷新 */
onRefresh() { onRefresh() {
this.onStartSearch() this.onStartSearch()
}, },
/** 点击 WiFi 项 → 进密码态 */
onTapWifi(e: WechatMiniprogram.TouchEvent) { onTapWifi(e: WechatMiniprogram.TouchEvent) {
const ssid = e.currentTarget.dataset.ssid as string const ssid = e.currentTarget.dataset.ssid as string
const target = this.data.wifiList.find(item => item.ssid === ssid) const target = this.data.wifiList.find(item => item.ssid === ssid)
if (!target || target.connected) return if (!target || target.connected) return
this.setData({ status: 'password', selectedSsid: ssid, selectWifiPWD: '', type: true })
this.setData({
status: 'password',
selectedWifi: { ssid },
selectWifiPWD: '',
type: true,
})
}, },
/** 密码输入 */
passwordInput(e: WechatMiniprogram.Input) { passwordInput(e: WechatMiniprogram.Input) {
this.setData({ selectWifiPWD: e.detail.value }) this.setData({ selectWifiPWD: e.detail.value })
}, },
/** 明文/密文切换 */
inputTypeChange() { inputTypeChange() {
this.setData({ type: !this.data.type }) this.setData({ type: !this.data.type })
}, },
/** 密码态「取消」→ 回列表 */
onCancelPwd() { onCancelPwd() {
this.setData({ this.setData({ status: 'list', selectedSsid: '', selectWifiPWD: '' })
status: 'list',
selectedWifi: { ssid: '' },
selectWifiPWD: '',
})
}, },
/** 密码态「连接」→ 将 WiFi 写入设备 */
onConfirmPwd() { onConfirmPwd() {
const { selectedWifi, selectWifiPWD } = this.data const { selectedSsid, selectWifiPWD } = this.data
if (!selectedWifi.ssid) return if (!selectedSsid) return
this.setData({ status: 'configuring' }) this.setData({ status: 'configuring' })
wx.showLoading({ title: '正在配网...', mask: true }) wx.showLoading({ title: '正在配网...', mask: true })
lefuService.configWifi(selectedSsid, selectWifiPWD, this._wifiVersion)
lefuService.configWifi(selectedWifi.ssid, selectWifiPWD, this._wifiVersion)
.then(() => { .then(() => {
wx.hideLoading() wx.hideLoading()
const next = this.data.wifiList.map(item => ({
...item,
connected: item.ssid === selectedWifi.ssid,
}))
this.setData({ this.setData({
status: 'list', status: 'list',
wifiList: next, wifiList: this.data.wifiList.map(item => ({ ...item, connected: item.ssid === selectedSsid })),
hasConnected: true, hasConnected: true,
selectedWifi: { ssid: '' }, selectedSsid: '',
selectWifiPWD: '', selectWifiPWD: '',
}) })
}) })
@@ -196,7 +125,6 @@ Page({
}) })
}, },
/** 底部「确认」:已有 WiFi 配网成功才可点击 */
onConfirm() { onConfirm() {
if (!this.data.hasConnected) return if (!this.data.hasConnected) return
wx.navigateTo({ url: '/pages/supplementPersonal/supplementPersonal' }) wx.navigateTo({ url: '/pages/supplementPersonal/supplementPersonal' })
@@ -7,7 +7,7 @@
<view> <view>
<image src="/images/connectedWifi/wifiPwd.png"/> <image src="/images/connectedWifi/wifiPwd.png"/>
</view> </view>
<view>{{selectedWifi.ssid}}</view> <view>{{selectedSsid}}</view>
</view> </view>
<view> <view>
<view>密码</view> <view>密码</view>
@@ -46,7 +46,7 @@
<view class="deviceCard"> <view class="deviceCard">
<view></view> <view></view>
<view> <view>
<view>{{currentDevice.name}}</view> <view>{{deviceName}}</view>
<view> <view>
<block wx:if="{{bleStatus === 'connected'}}"> <block wx:if="{{bleStatus === 'connected'}}">
<view class="status-dot status-dot--green"></view> <view class="status-dot status-dot--green"></view>
+2 -2
View File
@@ -15,13 +15,13 @@
"coverView": false, "coverView": false,
"postcss": false, "postcss": false,
"minified": false, "minified": false,
"enhance": false, "enhance": true,
"showShadowRootInWxmlPanel": false, "showShadowRootInWxmlPanel": false,
"packNpmRelationList": [], "packNpmRelationList": [],
"ignoreUploadUnusedFiles": true, "ignoreUploadUnusedFiles": true,
"compileHotReLoad": false, "compileHotReLoad": false,
"skylineRenderEnable": true, "skylineRenderEnable": true,
"es6": false, "es6": true,
"compileWorklet": false, "compileWorklet": false,
"uglifyFileName": false, "uglifyFileName": false,
"uploadWithSourceMap": true, "uploadWithSourceMap": true,
+1 -1
View File
@@ -2,7 +2,7 @@
"libVersion": "2.32.3", "libVersion": "2.32.3",
"projectname": "bodyWeight", "projectname": "bodyWeight",
"setting": { "setting": {
"urlCheck": true, "urlCheck": false,
"coverView": false, "coverView": false,
"lazyloadPlaceholderEnable": false, "lazyloadPlaceholderEnable": false,
"skylineRenderEnable": true, "skylineRenderEnable": true,