feat(lefu): 迁移乐福体脂秤 SDK,接入设备扫描连接与 WiFi 配网
- 新增 lefu 服务层(扫描/连接/断开/保活重连/测量回调/WiFi 配网),事件回调支持多监听 - connectedDevice 接入真实蓝牙扫描连接,修复断线重连 UI 未同步、蓝牙/定位开关误判 - connectedWifi 接入真实 WiFi 配网,补连接状态校验与退出配网模式 - 新增 5 张设备图标并调整应用入口 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
feb494080c
commit
f53f11e6e9
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"pages": [
|
"pages": [
|
||||||
|
"pages/connectedDevice/connectedDevice",
|
||||||
"pages/login/login",
|
"pages/login/login",
|
||||||
"pages/supplementPersonal/supplementPersonal",
|
"pages/supplementPersonal/supplementPersonal",
|
||||||
"pages/connectedDevice/connectedDevice",
|
|
||||||
"pages/connectedWifi/connectedWifi",
|
"pages/connectedWifi/connectedWifi",
|
||||||
"pages/home/home",
|
"pages/home/home",
|
||||||
"pages/detailedReport/detailedReport",
|
"pages/detailedReport/detailedReport",
|
||||||
|
|||||||
+9
-1
@@ -1,7 +1,15 @@
|
|||||||
App<IAppOption>({
|
App<IAppOption>({
|
||||||
globalData: {},
|
globalData: {},
|
||||||
|
|
||||||
onLaunch() {},
|
onLaunch() {
|
||||||
|
// 已登录则跳过登录页
|
||||||
|
const userInfo = wx.getStorageSync('userInfo') || null;
|
||||||
|
if (userInfo) {
|
||||||
|
wx.reLaunch({
|
||||||
|
url: '/pages/home/home'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getWxLoginCode(): Promise<string> {
|
getWxLoginCode(): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
@@ -0,0 +1,11 @@
|
|||||||
|
import type { LeFuConfig } from './types'
|
||||||
|
|
||||||
|
/** 乐福 SDK 配置 */
|
||||||
|
export const LEFU_CONFIG: LeFuConfig = {
|
||||||
|
key: 'lefudc91611833e18d94',
|
||||||
|
secret: 'eQ50JYimMp0X7uhNLj6D3lTEk4TUUvEG7dvaqKM9t1U=',
|
||||||
|
// domain1: 'http://10.10.10.10:8889',
|
||||||
|
// domain2: 'http://10.10.10.10:8889/weight',
|
||||||
|
domain1: 'http://device.shuziweidao.com:80/gateway',
|
||||||
|
domain2: 'http://device.shuziweidao.com:80/weight',
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import type { DeviceSetting } from './types'
|
||||||
|
|
||||||
|
/** 支持的设备型号配置列表(5款) */
|
||||||
|
export 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,
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export { leFuService } from './service'
|
||||||
|
export type {
|
||||||
|
ScannedDevice,
|
||||||
|
LeFuWifiItem,
|
||||||
|
ProgressData,
|
||||||
|
LockData,
|
||||||
|
LeFuConfig,
|
||||||
|
DeviceSetting,
|
||||||
|
DeviceInfo,
|
||||||
|
} from './types'
|
||||||
@@ -0,0 +1,410 @@
|
|||||||
|
import { LEFU_CONFIG } from './config'
|
||||||
|
import { DEVICE_SETTINGS } from './deviceSettings'
|
||||||
|
import type {
|
||||||
|
RawDevice,
|
||||||
|
LeFuPlugin,
|
||||||
|
ScannedDevice,
|
||||||
|
LeFuWifiItem,
|
||||||
|
ProgressData,
|
||||||
|
LockData,
|
||||||
|
LeFuConfig,
|
||||||
|
DeviceInfo,
|
||||||
|
} from './types'
|
||||||
|
|
||||||
|
/** 顶层加载插件(对齐 demo,requirePlugin 必须在文件顶层调用) */
|
||||||
|
const plugin = requirePlugin('ppScale-plugin') as LeFuPlugin
|
||||||
|
|
||||||
|
/** 统一日志前缀,便于定位问题 */
|
||||||
|
const TAG = '[LeFuService]'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 乐福体脂秤服务(单例)
|
||||||
|
* 只保留 SDK 底层必需能力:蓝牙扫描、连接、断开、测量回调、保活与重连。
|
||||||
|
* 业务层能力(WiFi 配网、用户同步、清空成员等)不在此维护。
|
||||||
|
*/
|
||||||
|
class LeFuService {
|
||||||
|
// ─── 内部状态 ───────────────────────────────
|
||||||
|
/** 当前激活的协议实例,非空表示已连接 */
|
||||||
|
private _activeProtocol: any = null
|
||||||
|
/** 当前蓝牙连接状态(取值来自 plugin.BLUE_STATE) */
|
||||||
|
private _connectState = ''
|
||||||
|
/** 设备固件信息 */
|
||||||
|
private _deviceInfo: DeviceInfo | null = null
|
||||||
|
/** 总线是否已订阅(避免重复 subscribe) */
|
||||||
|
private _busSubscribed = false
|
||||||
|
/** 是否为用户主动发起的连接/断开(用于区分是否自动重连) */
|
||||||
|
private _intentionalConnect = false
|
||||||
|
|
||||||
|
/** 已重连次数 */
|
||||||
|
private _reconnectCount = 0
|
||||||
|
/** 最大自动重连次数 */
|
||||||
|
private readonly _maxReconnect = 3
|
||||||
|
/** 重连定时器 */
|
||||||
|
private _reconnectTimer: number | null = null
|
||||||
|
/** 保活心跳定时器 */
|
||||||
|
private _keepAliveTimer: number | null = null
|
||||||
|
/** 最近一次连接/扫描到的原始设备(用于断线重连) */
|
||||||
|
private _lastRawDevice: RawDevice | null = null
|
||||||
|
|
||||||
|
// ─── 事件回调(每个事件支持多个监听者,用 Set 存储) ───
|
||||||
|
private _onDevicesListCbs: Set<(devices: ScannedDevice[]) => void> = new Set()
|
||||||
|
private _onConnectStateCbs: Set<(state: string) => void> = new Set()
|
||||||
|
private _onDeviceConnectCbs: Set<() => void> = new Set()
|
||||||
|
private _onProgressCbs: Set<(data: ProgressData) => void> = new Set()
|
||||||
|
private _onLockedCbs: Set<(data: LockData) => void> = new Set()
|
||||||
|
private _onDisconnectedCbs: Set<() => void> = new Set()
|
||||||
|
private _onDeviceInfoCbs: Set<(info: DeviceInfo) => void> = new Set()
|
||||||
|
|
||||||
|
// ─── 只读状态访问器 ─────────────────────────
|
||||||
|
/** SDK 配置(key/secret 等) */
|
||||||
|
get config(): LeFuConfig { return LEFU_CONFIG }
|
||||||
|
/** 是否已连接(存在激活协议实例) */
|
||||||
|
get isConnected(): boolean { return !!this._activeProtocol }
|
||||||
|
/** 当前连接状态字符串 */
|
||||||
|
get connectState(): string { return this._connectState }
|
||||||
|
/** 蓝牙状态枚举(由插件提供,兜底为空对象) */
|
||||||
|
get BLUE_STATE(): Record<string, string> { return plugin.BLUE_STATE ?? {} }
|
||||||
|
/** 设备固件信息 */
|
||||||
|
get deviceInfo(): DeviceInfo | null { return this._deviceInfo }
|
||||||
|
|
||||||
|
// ─── 事件回调注册(均返回反注册函数) ───────
|
||||||
|
/** 订阅「扫描结果列表」事件 */
|
||||||
|
onDevicesList(cb: (devices: ScannedDevice[]) => void): () => void { this._onDevicesListCbs.add(cb); return () => { this._onDevicesListCbs.delete(cb) } }
|
||||||
|
/** 订阅「连接状态变化」事件 */
|
||||||
|
onConnectState(cb: (state: string) => void): () => void { this._onConnectStateCbs.add(cb); return () => { this._onConnectStateCbs.delete(cb) } }
|
||||||
|
/** 订阅「设备已连接」事件 */
|
||||||
|
onDeviceConnect(cb: () => void): () => void { this._onDeviceConnectCbs.add(cb); return () => { this._onDeviceConnectCbs.delete(cb) } }
|
||||||
|
/** 订阅「测量进度」事件 */
|
||||||
|
onMeasuring(cb: (data: ProgressData) => void): () => void { this._onProgressCbs.add(cb); return () => { this._onProgressCbs.delete(cb) } }
|
||||||
|
/** 订阅「测量锁定结果」事件 */
|
||||||
|
onLocked(cb: (data: LockData) => void): () => void { this._onLockedCbs.add(cb); return () => { this._onLockedCbs.delete(cb) } }
|
||||||
|
/** 订阅「断开连接」事件 */
|
||||||
|
onDisconnected(cb: () => void): () => void { this._onDisconnectedCbs.add(cb); return () => { this._onDisconnectedCbs.delete(cb) } }
|
||||||
|
/** 订阅「设备固件信息」事件 */
|
||||||
|
onDeviceInfo(cb: (info: DeviceInfo) => void): () => void { this._onDeviceInfoCbs.add(cb); return () => { this._onDeviceInfoCbs.delete(cb) } }
|
||||||
|
|
||||||
|
// ─── 扫描与连接 ─────────────────────────────
|
||||||
|
/**
|
||||||
|
* 开始扫描设备
|
||||||
|
* 初始化插件设备配置、订阅总线事件,然后启动蓝牙扫描。
|
||||||
|
*/
|
||||||
|
startScan(): void {
|
||||||
|
console.log(TAG, 'startScan()')
|
||||||
|
plugin.Blue.setDeviceSetting(DEVICE_SETTINGS)
|
||||||
|
this._subscribeBus()
|
||||||
|
plugin.Blue.start(DEVICE_SETTINGS.map(s => s.deviceName), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅插件总线事件(幂等,只订阅一次)
|
||||||
|
* 将底层 SDK 事件桥接为服务层的回调。
|
||||||
|
*/
|
||||||
|
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._onDevicesListCbs.forEach(cb => cb([]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const devices: ScannedDevice[] = devList.map((item: RawDevice) => ({
|
||||||
|
name: item.name || item.deviceName || '未知设备',
|
||||||
|
raw: item,
|
||||||
|
model: {},
|
||||||
|
}))
|
||||||
|
this._onDevicesListCbs.forEach(cb => cb(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._onDeviceInfoCbs.forEach(cb => cb(res))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设备连接成功:重置重连计数,初始化协议实例并同步时间/MTU
|
||||||
|
plugin.bus.subscribe('deviceConnect', () => {
|
||||||
|
console.log(TAG, 'bus[deviceConnect]')
|
||||||
|
this._intentionalConnect = false
|
||||||
|
this._reconnectCount = 0
|
||||||
|
plugin.ScaleAction.startDataProgress(true)
|
||||||
|
this._activeProtocol = plugin.ScaleAction.getActiveProtocol()
|
||||||
|
if (!this._activeProtocol) {
|
||||||
|
console.error(TAG, 'getActiveProtocol() 返回空,无法完成连接初始化')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 更新 MTU 后同步时间,全部完成再通知业务层「已连接」
|
||||||
|
this._activeProtocol.codeUpdateMTU((mtuRes: any) => {
|
||||||
|
console.log(TAG, 'codeUpdateMTU:', mtuRes)
|
||||||
|
this._activeProtocol.codeSyncTime((syncRes: any) => {
|
||||||
|
console.log(TAG, 'codeSyncTime:', syncRes)
|
||||||
|
this._onDeviceConnectCbs.forEach(cb => cb())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 连接状态变化:驱动保活、重连与清理
|
||||||
|
plugin.bus.subscribe('connectState', (res: string) => {
|
||||||
|
console.log(TAG, 'bus[connectState]:', res)
|
||||||
|
this._connectState = res
|
||||||
|
this._onConnectStateCbs.forEach(cb => cb(res))
|
||||||
|
// 连接成功:启动保活心跳
|
||||||
|
if (res === plugin.BLUE_STATE.CONNECTSUCCESS) {
|
||||||
|
this._startKeepAlive()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 连接失败:停止保活,尝试重连
|
||||||
|
if (res === plugin.BLUE_STATE.CONNECTFAILED) {
|
||||||
|
this._stopKeepAlive()
|
||||||
|
this._intentionalConnect = false
|
||||||
|
this._doReconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 蓝牙不可用:做完整清理
|
||||||
|
if (res === plugin.BLUE_STATE.UNAVAILABLE) {
|
||||||
|
this._stopKeepAlive()
|
||||||
|
this._stopReconnectTimer()
|
||||||
|
if (this._activeProtocol) {
|
||||||
|
this._activeProtocol = null
|
||||||
|
this._onDisconnectedCbs.forEach(cb => cb())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 蓝牙就绪:如有缓存设备且非主动断开,尝试重连
|
||||||
|
if (res === plugin.BLUE_STATE.READY) {
|
||||||
|
if (this._intentionalConnect) {
|
||||||
|
this._intentionalConnect = false
|
||||||
|
} else if (this._lastRawDevice && !this._activeProtocol) {
|
||||||
|
this._stopReconnectTimer()
|
||||||
|
wx.openBluetoothAdapter({
|
||||||
|
success: () => this.connect(this._lastRawDevice!),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测量进度:透传给业务层
|
||||||
|
plugin.bus.subscribe('progressData', (res: ProgressData) => {
|
||||||
|
this._onProgressCbs.forEach(cb => cb(res))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测量锁定结果:透传给业务层
|
||||||
|
plugin.bus.subscribe('lockData', (res: LockData) => {
|
||||||
|
this._onLockedCbs.forEach(cb => cb(res))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 即将断开:清理协议实例,若非主动断开则触发重连
|
||||||
|
plugin.bus.subscribe('deviceWillDisconnect', () => {
|
||||||
|
console.warn(TAG, 'bus[deviceWillDisconnect],intentionalConnect:', this._intentionalConnect)
|
||||||
|
this._stopKeepAlive()
|
||||||
|
this._activeProtocol = null
|
||||||
|
this._onDisconnectedCbs.forEach(cb => cb())
|
||||||
|
if (!this._intentionalConnect) {
|
||||||
|
this._doReconnect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接指定设备
|
||||||
|
* 记录缓存设备、标记主动连接,清理旧状态后创建新的 BLE 连接。
|
||||||
|
*/
|
||||||
|
connect(rawDevice: RawDevice): void {
|
||||||
|
console.log(TAG, 'connect():', rawDevice.name)
|
||||||
|
this._lastRawDevice = rawDevice
|
||||||
|
this._intentionalConnect = true
|
||||||
|
this._stopKeepAlive()
|
||||||
|
this._stopReconnectTimer()
|
||||||
|
// 先断开旧连接(此时状态仍在),再清空内部状态
|
||||||
|
if (this.isConnected) {
|
||||||
|
plugin.Blue.disconnect()
|
||||||
|
}
|
||||||
|
this._activeProtocol = null
|
||||||
|
this._connectState = ''
|
||||||
|
this._deviceInfo = null
|
||||||
|
// 直连缓存设备时不走 startScan,需手动初始化插件设备配置和总线订阅
|
||||||
|
plugin.Blue.setDeviceSetting(DEVICE_SETTINGS)
|
||||||
|
this._subscribeBus()
|
||||||
|
plugin.Blue.stopBluetoothDevicesDiscovery()
|
||||||
|
plugin.Blue.clearProtocol()
|
||||||
|
plugin.Blue.createBLEConnection(rawDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主动断开连接
|
||||||
|
* 标记主动断开以阻止自动重连,并停止保活。
|
||||||
|
*/
|
||||||
|
disconnect(): void {
|
||||||
|
console.log(TAG, 'disconnect()')
|
||||||
|
this._intentionalConnect = true
|
||||||
|
this._stopKeepAlive()
|
||||||
|
plugin.Blue.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止扫描
|
||||||
|
* 停止蓝牙设备发现、清除重连定时器与扫描回调。
|
||||||
|
*/
|
||||||
|
stopScan(): void {
|
||||||
|
console.log(TAG, 'stopScan()')
|
||||||
|
this._stopReconnectTimer()
|
||||||
|
wx.stopBluetoothDevicesDiscovery()
|
||||||
|
this._onDevicesListCbs.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── WiFi 配网 ─────────────────────────────
|
||||||
|
/**
|
||||||
|
* 获取周围 WiFi 列表
|
||||||
|
* 依赖已连接的设备协议实例,下发查询指令后返回附近热点。
|
||||||
|
*/
|
||||||
|
getWifiList(): Promise<LeFuWifiItem[]> {
|
||||||
|
console.log(TAG, 'getWifiList()')
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this._activeProtocol) {
|
||||||
|
reject(new Error('设备未连接,无法获取 WiFi 列表'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let timer: number | null = null
|
||||||
|
let delayTimer: number | null = null
|
||||||
|
// 15s 整体超时
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
if (delayTimer !== null) { clearTimeout(delayTimer); delayTimer = null }
|
||||||
|
reject(new Error('获取 WiFi 列表超时'))
|
||||||
|
}, 15000)
|
||||||
|
// 延时 2s 后下发查询指令(等待设备进入配网模式)
|
||||||
|
delayTimer = setTimeout(() => {
|
||||||
|
this._activeProtocol.dataFindSurroundDevice((res: any[]) => {
|
||||||
|
if (timer !== null) { clearTimeout(timer); timer = null }
|
||||||
|
if (delayTimer !== null) { clearTimeout(delayTimer); delayTimer = null }
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置设备连接指定 WiFi
|
||||||
|
* 型号含 5 段走新协议 domain2,否则走老协议 domain1;成功返回码为 23。
|
||||||
|
*/
|
||||||
|
configWifi(ssid: string, password: string): Promise<void> {
|
||||||
|
const version = (this._deviceInfo?.modelNumber ?? '').split('-').length === 5 ? 'domain2' : 'domain1'
|
||||||
|
console.log(TAG, 'configWifi():', ssid, version, 'activeProtocol=', !!this._activeProtocol)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this._activeProtocol) {
|
||||||
|
reject(new Error('设备未连接,无法配网'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let settled = false
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (settled) return
|
||||||
|
settled = true
|
||||||
|
reject(new Error('配网超时,请重试'))
|
||||||
|
}, 20000)
|
||||||
|
const callback = (res: number) => {
|
||||||
|
if (settled) return
|
||||||
|
settled = true
|
||||||
|
clearTimeout(timer)
|
||||||
|
res === 23 ? resolve() : reject(new Error(`配网失败,错误码:${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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备是否已配置 WiFi
|
||||||
|
* 返回码 0x01 表示已配网。
|
||||||
|
*/
|
||||||
|
checkWifiConfig(): Promise<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this._activeProtocol) { reject(new Error('设备未连接')); return }
|
||||||
|
this._activeProtocol.codeFetchWifiConfig((res: number | undefined | null) => {
|
||||||
|
resolve(res === 0x01)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出配网模式
|
||||||
|
* 配网流程结束(含页面退出)时调用,让设备退出 WiFi 配置状态。
|
||||||
|
*/
|
||||||
|
exitWifiConfig(): void {
|
||||||
|
if (!this._activeProtocol) return
|
||||||
|
this._activeProtocol.dataExitWifiConfig(() => {
|
||||||
|
console.log(TAG, '退出配网模式完成')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 私有:保活与重连 ───────────────────────
|
||||||
|
/** 启动保活心跳:文档建议每 10s 发送一次 keepAlive,防止设备主动断开 */
|
||||||
|
private _startKeepAlive(): void {
|
||||||
|
this._stopKeepAlive()
|
||||||
|
this._keepAliveTimer = setInterval(() => { this._activeProtocol?.sendKeepAliveCode?.() }, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 停止保活心跳定时器 */
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行自动重连
|
||||||
|
* 超过最大次数则放弃;否则延时 2s 后重建连接。
|
||||||
|
*/
|
||||||
|
private _doReconnect(): void {
|
||||||
|
if (this._reconnectCount >= this._maxReconnect) {
|
||||||
|
console.warn(TAG, '重连次数已达上限')
|
||||||
|
this._onDisconnectedCbs.forEach(cb => cb())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this._lastRawDevice) {
|
||||||
|
console.warn(TAG, '无缓存设备,放弃重连')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._reconnectCount++
|
||||||
|
console.log(TAG, `_doReconnect 第 ${this._reconnectCount} 次`)
|
||||||
|
this._stopReconnectTimer()
|
||||||
|
this._reconnectTimer = setTimeout(() => {
|
||||||
|
plugin.Blue.clearProtocol()
|
||||||
|
plugin.Blue.createBLEConnection(this._lastRawDevice!)
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出单例实例 */
|
||||||
|
export const leFuService = new LeFuService()
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/** ppScale-plugin 原始设备对象 */
|
||||||
|
export type RawDevice = Record<string, any>
|
||||||
|
|
||||||
|
/** 乐福 SDK 插件对象(运行时由 requirePlugin 注入) */
|
||||||
|
export type LeFuPlugin = Record<string, any>
|
||||||
|
|
||||||
|
|
||||||
|
/** 设备配置(来自 master device.setting 列表) */
|
||||||
|
export interface DeviceSetting {
|
||||||
|
advLength: number
|
||||||
|
calorieStatus: number
|
||||||
|
deviceAccuracyType: number
|
||||||
|
deviceCalcuteType: number
|
||||||
|
deviceConnectType: number
|
||||||
|
deviceFuncType: number
|
||||||
|
deviceName: string
|
||||||
|
devicePowerType: number
|
||||||
|
deviceProtocolType: number
|
||||||
|
deviceType: number
|
||||||
|
deviceUnitType: string
|
||||||
|
id: number
|
||||||
|
macAddressStart: number
|
||||||
|
sign: string
|
||||||
|
status: number
|
||||||
|
uhStatus: number
|
||||||
|
createBy: null
|
||||||
|
imgUrl: null
|
||||||
|
remark: null
|
||||||
|
updateBy: null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 扫描到的设备(展示层使用) */
|
||||||
|
export interface ScannedDevice {
|
||||||
|
/** 设备名称 */
|
||||||
|
name: string
|
||||||
|
/** plugin 返回的原始设备对象,连接时传回给 SDK */
|
||||||
|
raw: RawDevice
|
||||||
|
/** 设备模型(含 deviceConnectType 等信息) */
|
||||||
|
model: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/** WiFi 项 */
|
||||||
|
export interface LeFuWifiItem {
|
||||||
|
/** WiFi SSID */
|
||||||
|
ssid: string
|
||||||
|
/** 信号强度(部分设备上报) */
|
||||||
|
signal?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 测量进度数据(progressData 事件,测量过程中多次回调) */
|
||||||
|
export interface ProgressData {
|
||||||
|
/** 体重,单位:克 */
|
||||||
|
weight: number
|
||||||
|
/** 单位枚举值 */
|
||||||
|
unit: number
|
||||||
|
/** 是否正在心率测量 */
|
||||||
|
isHeartRating: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 测量锁定数据(lockData 事件,测量完成时回调一次) */
|
||||||
|
export interface LockData {
|
||||||
|
/** 体重,单位:克 */
|
||||||
|
weight: number
|
||||||
|
/** 阻抗值(用于体成分分析) */
|
||||||
|
resistance: number
|
||||||
|
/** 心率(部分设备上报) */
|
||||||
|
heartRate?: number
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 乐福服务配置 */
|
||||||
|
export interface LeFuConfig {
|
||||||
|
key: string
|
||||||
|
secret: string
|
||||||
|
/** 老协议域名(无鉴权) */
|
||||||
|
domain1: string
|
||||||
|
/** 新协议域名(有鉴权) */
|
||||||
|
domain2: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设备固件信息(deviceInfo 事件返回) */
|
||||||
|
export interface DeviceInfo {
|
||||||
|
/** 固件版本号,格式:mcu.ble.wifi.res,如 "006.005.004.305" */
|
||||||
|
firmwareRevision: string
|
||||||
|
/** 设备型号,用于请求服务端版本接口 */
|
||||||
|
modelNumber: string
|
||||||
|
/** 设备序列号 */
|
||||||
|
serialNumber: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
@@ -1,21 +1,30 @@
|
|||||||
/** 展示用设备项 */
|
import { leFuService } from '../../lefu/index'
|
||||||
|
import type { ScannedDevice } from '../../lefu/index'
|
||||||
|
import type { RawDevice } from '../../lefu/types'
|
||||||
|
|
||||||
|
/** 渲染用的设备项(RawDevice 含复杂对象,不进 setData) */
|
||||||
interface DeviceItem {
|
interface DeviceItem {
|
||||||
name: string
|
name: string
|
||||||
connected: boolean
|
connected: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 页面状态 */
|
|
||||||
type DeviceStatus = 'idle' | 'searching' | 'found' | 'empty'
|
type DeviceStatus = 'idle' | 'searching' | 'found' | 'empty'
|
||||||
|
|
||||||
/** 假设备数据 */
|
/** 扫描到的原始设备缓存 */
|
||||||
const MOCK_DEVICES: DeviceItem[] = [
|
let scannedDevices: ScannedDevice[] = []
|
||||||
{ name: '乐福体脂秤 Pro', connected: false },
|
/** 扫描 / 断开回调的取消订阅句柄 */
|
||||||
{ name: '乐福体脂秤 Air', connected: false },
|
let unsubDevicesList: (() => void) | null = null
|
||||||
{ name: '乐福体脂秤 Mini', connected: false },
|
let unsubDisconnected: (() => void) | null = null
|
||||||
]
|
let unsubDeviceConnect: (() => void) | null = null
|
||||||
|
/** 连接流程的清理句柄 */
|
||||||
/** 扫描定时器 */
|
let connectCleanup: {
|
||||||
let _searchTimer: number | null = null
|
timer: number | null
|
||||||
|
unsubState: (() => void) | null
|
||||||
|
unsubConnect: (() => void) | null
|
||||||
|
} | null = null
|
||||||
|
/** 一次性防重入标记 */
|
||||||
|
let modalLocked = false
|
||||||
|
let autoConnectTried = false
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
@@ -23,34 +32,33 @@ Page({
|
|||||||
deviceList: [] as DeviceItem[],
|
deviceList: [] as DeviceItem[],
|
||||||
},
|
},
|
||||||
|
|
||||||
onHide() {
|
onShow() {
|
||||||
this._clearSearchTimer()
|
// 从其他页面返回时同步连接状态
|
||||||
},
|
if (!leFuService.isConnected) {
|
||||||
|
this._markAllDisconnected()
|
||||||
onUnload() {
|
|
||||||
this._clearSearchTimer()
|
|
||||||
},
|
|
||||||
|
|
||||||
_clearSearchTimer() {
|
|
||||||
if (_searchTimer !== null) {
|
|
||||||
clearTimeout(_searchTimer)
|
|
||||||
_searchTimer = null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 点击「开始搜索 / 重新搜索」:模拟扫描 */
|
onHide() {
|
||||||
onStartSearch() {
|
leFuService.stopScan()
|
||||||
this._clearSearchTimer()
|
|
||||||
this.setData({ status: 'searching', deviceList: [] })
|
|
||||||
_searchTimer = setTimeout(() => {
|
|
||||||
this.setData({
|
|
||||||
status: 'found',
|
|
||||||
deviceList: MOCK_DEVICES.map(item => ({ ...item })),
|
|
||||||
})
|
|
||||||
}, 1500)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 点击整张卡片:已连接 → 跳转配网页;未连接 → 走连接流程 */
|
onUnload() {
|
||||||
|
leFuService.stopScan()
|
||||||
|
this._clearSubscriptions()
|
||||||
|
this._clearConnectCleanup()
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 点击「开始 / 重新搜索」 */
|
||||||
|
async onStartSearch() {
|
||||||
|
if (modalLocked) return
|
||||||
|
scannedDevices = []
|
||||||
|
autoConnectTried = false
|
||||||
|
this.setData({ status: 'searching', deviceList: [] })
|
||||||
|
await this._requestPermissionAndScan()
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 点击设备卡片:已连接跳配网,未连接走连接流程 */
|
||||||
onTapCard(e: WechatMiniprogram.TouchEvent) {
|
onTapCard(e: WechatMiniprogram.TouchEvent) {
|
||||||
const connected = e.currentTarget.dataset.connected as boolean
|
const connected = e.currentTarget.dataset.connected as boolean
|
||||||
if (connected) {
|
if (connected) {
|
||||||
@@ -60,24 +68,220 @@ Page({
|
|||||||
this.onConnect(e)
|
this.onConnect(e)
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 模拟连接指定设备 */
|
/** 手动连接指定设备 */
|
||||||
onConnect(e: WechatMiniprogram.TouchEvent) {
|
onConnect(e: WechatMiniprogram.TouchEvent) {
|
||||||
const index = e.currentTarget.dataset.index as number
|
const index = e.currentTarget.dataset.index as number
|
||||||
const list = this.data.deviceList.map((item, i) => ({
|
this._connectByIndex(index, false)
|
||||||
...item,
|
|
||||||
connected: i === index,
|
|
||||||
}))
|
|
||||||
this.setData({ deviceList: list })
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 断开当前连接 */
|
/** 断开当前连接 */
|
||||||
onDisconnect() {
|
onDisconnect() {
|
||||||
const list = this.data.deviceList.map(item => ({ ...item, connected: false }))
|
this._markAllDisconnected()
|
||||||
this.setData({ deviceList: list })
|
leFuService.disconnect()
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 查看帮助 */
|
|
||||||
onOpenHelp() {
|
onOpenHelp() {
|
||||||
wx.showToast({ title: '请确保设备已开机且在附近', icon: 'none' })
|
wx.showToast({ title: '请确保设备已开机且在附近', icon: 'none' })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 将所有卡片置为未连接 */
|
||||||
|
_markAllDisconnected() {
|
||||||
|
this.setData({
|
||||||
|
deviceList: this.data.deviceList.map(item => ({ ...item, connected: false })),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 连接成功后把对应设备卡片标记为已连接(覆盖手动连接与自动重连) */
|
||||||
|
_markConnected() {
|
||||||
|
const cached = wx.getStorageSync('connectDeviceInfo') as Record<string, any> | null
|
||||||
|
const name = leFuService.deviceInfo?.modelNumber
|
||||||
|
|| cached?.scaleDeviceName
|
||||||
|
|| cached?.name
|
||||||
|
|| cached?.deviceName
|
||||||
|
if (!name) return
|
||||||
|
this.setData({
|
||||||
|
deviceList: this.data.deviceList.map(item => ({ ...item, connected: item.name === name })),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
_clearSubscriptions() {
|
||||||
|
unsubDevicesList?.()
|
||||||
|
unsubDisconnected?.()
|
||||||
|
unsubDeviceConnect?.()
|
||||||
|
unsubDevicesList = null
|
||||||
|
unsubDisconnected = null
|
||||||
|
unsubDeviceConnect = null
|
||||||
|
},
|
||||||
|
|
||||||
|
_clearConnectCleanup() {
|
||||||
|
if (!connectCleanup) return
|
||||||
|
if (connectCleanup.timer !== null) clearTimeout(connectCleanup.timer)
|
||||||
|
connectCleanup.unsubState?.()
|
||||||
|
connectCleanup.unsubConnect?.()
|
||||||
|
connectCleanup = null
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 申请位置权限后打开蓝牙适配器(蓝牙权限由 openBluetoothAdapter 自动触发) */
|
||||||
|
async _requestPermissionAndScan() {
|
||||||
|
try {
|
||||||
|
const { authSetting } = await wx.getSetting()
|
||||||
|
const setting = authSetting as Record<string, boolean | undefined>
|
||||||
|
if (!setting['scope.userLocation']) {
|
||||||
|
await wx.authorize({ scope: 'scope.userLocation' })
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this._showLocationGuide()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this._openAdapterAndScan()
|
||||||
|
},
|
||||||
|
|
||||||
|
async _openAdapterAndScan() {
|
||||||
|
try {
|
||||||
|
await wx.openBluetoothAdapter()
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('[connectedDevice] openBluetoothAdapter 失败:', err)
|
||||||
|
this.setData({ status: 'idle' })
|
||||||
|
this._handleAdapterError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._startScan()
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 区分蓝牙/定位服务开关,给出准确提示 */
|
||||||
|
_handleAdapterError() {
|
||||||
|
try {
|
||||||
|
const setting = wx.getSystemSetting()
|
||||||
|
if (!setting.bluetoothEnabled) {
|
||||||
|
this._showBluetoothOff()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!setting.locationEnabled) {
|
||||||
|
this._showLocationOff()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 基础库不支持 wx.getSystemSetting 时,退化为通用提示
|
||||||
|
}
|
||||||
|
wx.showToast({ title: '蓝牙初始化失败,请重试', icon: 'none' })
|
||||||
|
},
|
||||||
|
|
||||||
|
_startScan() {
|
||||||
|
this._clearSubscriptions()
|
||||||
|
unsubDevicesList = leFuService.onDevicesList((devices) => {
|
||||||
|
scannedDevices = devices
|
||||||
|
if (!devices.length) {
|
||||||
|
this.setData({ status: 'empty', deviceList: [] })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const connectedName = this.data.deviceList.find(d => d.connected)?.name
|
||||||
|
this.setData({
|
||||||
|
status: 'found',
|
||||||
|
deviceList: devices.map(d => ({ name: d.name, connected: d.name === connectedName })),
|
||||||
|
})
|
||||||
|
this._tryAutoConnect()
|
||||||
|
})
|
||||||
|
unsubDisconnected = leFuService.onDisconnected(() => {
|
||||||
|
this._markAllDisconnected()
|
||||||
|
})
|
||||||
|
unsubDeviceConnect = leFuService.onDeviceConnect(() => {
|
||||||
|
this._markConnected()
|
||||||
|
})
|
||||||
|
leFuService.startScan()
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 命中缓存设备时自动连接一次 */
|
||||||
|
_tryAutoConnect() {
|
||||||
|
if (autoConnectTried) return
|
||||||
|
const cached = wx.getStorageSync('connectDeviceInfo') as RawDevice | null
|
||||||
|
if (!cached?.deviceId) return
|
||||||
|
const index = scannedDevices.findIndex(d => d.raw.deviceId === cached.deviceId)
|
||||||
|
if (index < 0) return
|
||||||
|
autoConnectTried = true
|
||||||
|
this._connectByIndex(index, true)
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 统一连接流程(手动 / 自动共用),手动连接成功后跳配网 */
|
||||||
|
_connectByIndex(index: number, isAuto: boolean) {
|
||||||
|
const scanned = scannedDevices[index]
|
||||||
|
if (!scanned) return
|
||||||
|
|
||||||
|
wx.showLoading({ title: '连接中...', mask: true })
|
||||||
|
leFuService.stopScan()
|
||||||
|
leFuService.connect(scanned.raw)
|
||||||
|
|
||||||
|
this._clearConnectCleanup()
|
||||||
|
connectCleanup = { timer: null, unsubState: null, unsubConnect: null }
|
||||||
|
|
||||||
|
const finish = () => {
|
||||||
|
if (connectCleanup?.timer !== null) clearTimeout(connectCleanup.timer)
|
||||||
|
connectCleanup?.unsubState?.()
|
||||||
|
connectCleanup?.unsubConnect?.()
|
||||||
|
connectCleanup = null
|
||||||
|
wx.hideLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
connectCleanup.timer = setTimeout(() => {
|
||||||
|
finish()
|
||||||
|
wx.showToast({ title: '连接超时', icon: 'none' })
|
||||||
|
}, 15000)
|
||||||
|
|
||||||
|
connectCleanup.unsubState = leFuService.onConnectState((state) => {
|
||||||
|
if (state === leFuService.BLUE_STATE.CONNECTFAILED) {
|
||||||
|
finish()
|
||||||
|
wx.showToast({ title: '连接失败,请重试', icon: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
connectCleanup.unsubConnect = leFuService.onDeviceConnect(() => {
|
||||||
|
finish()
|
||||||
|
wx.setStorageSync('connectDeviceInfo', { ...scanned.raw, scaleDeviceName: scanned.raw.name })
|
||||||
|
if (!isAuto) {
|
||||||
|
wx.navigateTo({ url: '/pages/connectedWifi/connectedWifi' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
_showBluetoothOff() {
|
||||||
|
wx.showModal({
|
||||||
|
title: '蓝牙未开启',
|
||||||
|
content: '请先开启手机蓝牙后重试',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '知道了',
|
||||||
|
success: () => this.setData({ status: 'idle' }),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
_showLocationOff() {
|
||||||
|
wx.showModal({
|
||||||
|
title: '定位服务未开启',
|
||||||
|
content: '安卓手机搜索蓝牙设备需开启定位服务,请前往系统设置开启后重试。',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '知道了',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
_showLocationGuide() {
|
||||||
|
modalLocked = true
|
||||||
|
wx.showModal({
|
||||||
|
title: '需要授权',
|
||||||
|
content: '小程序需要位置权限才能搜索蓝牙设备,请前往设置开启。',
|
||||||
|
confirmText: '去开启',
|
||||||
|
cancelText: '取消',
|
||||||
|
success: (res) => {
|
||||||
|
modalLocked = false
|
||||||
|
this.setData({ status: 'idle' })
|
||||||
|
if (res.confirm) {
|
||||||
|
wx.openSetting({
|
||||||
|
success: () => this.onStartSearch(),
|
||||||
|
fail: () => this.setData({ status: 'idle' }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
modalLocked = false
|
||||||
|
this.setData({ status: 'idle' })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { leFuService } from '../../lefu/index'
|
||||||
|
|
||||||
|
/** 连接状态事件的取消订阅句柄 */
|
||||||
|
let unsubDisconnected: (() => void) | null = null
|
||||||
|
let unsubDeviceConnect: (() => void) | null = null
|
||||||
|
|
||||||
|
/** 渲染用的 WiFi 项(在 SSID 基础上追加连接状态) */
|
||||||
interface WifiItem {
|
interface WifiItem {
|
||||||
ssid: string
|
ssid: string
|
||||||
connected: boolean
|
connected: boolean
|
||||||
@@ -6,17 +13,6 @@ interface WifiItem {
|
|||||||
type WifiStatus = 'idle' | 'searching' | 'list' | 'empty' | 'password' | 'configuring'
|
type WifiStatus = 'idle' | 'searching' | 'list' | 'empty' | 'password' | 'configuring'
|
||||||
type BleStatus = 'connected' | 'failed' | 'connecting'
|
type BleStatus = 'connected' | 'failed' | 'connecting'
|
||||||
|
|
||||||
/** 假 WiFi 列表 */
|
|
||||||
const MOCK_WIFI: WifiItem[] = [
|
|
||||||
{ ssid: 'TP-LINK_5G_8A3C', connected: false },
|
|
||||||
{ ssid: 'Home_WiFi', connected: false },
|
|
||||||
{ ssid: 'Xiaomi_1234', connected: false },
|
|
||||||
]
|
|
||||||
|
|
||||||
/** 搜索 / 配网定时器 */
|
|
||||||
let _searchTimer: number | null = null
|
|
||||||
let _configTimer: number | null = null
|
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
status: 'idle' as WifiStatus,
|
status: 'idle' as WifiStatus,
|
||||||
@@ -30,31 +26,74 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onLoad() {
|
onLoad() {
|
||||||
|
this._initDeviceInfo()
|
||||||
|
this._subscribeDisconnect()
|
||||||
this.onStartSearch()
|
this.onStartSearch()
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnload() {
|
onUnload() {
|
||||||
wx.hideLoading()
|
wx.hideLoading()
|
||||||
this._clearTimers()
|
unsubDisconnected?.()
|
||||||
|
unsubDeviceConnect?.()
|
||||||
|
unsubDisconnected = null
|
||||||
|
unsubDeviceConnect = null
|
||||||
|
leFuService.exitWifiConfig()
|
||||||
},
|
},
|
||||||
|
|
||||||
_clearTimers() {
|
/** 订阅连接状态事件:断开 / 重连时同步状态到 UI */
|
||||||
if (_searchTimer !== null) { clearTimeout(_searchTimer); _searchTimer = null }
|
_subscribeDisconnect() {
|
||||||
if (_configTimer !== null) { clearTimeout(_configTimer); _configTimer = null }
|
unsubDisconnected?.()
|
||||||
|
unsubDeviceConnect?.()
|
||||||
|
unsubDisconnected = leFuService.onDisconnected(() => {
|
||||||
|
this.setData({ bleStatus: 'failed' })
|
||||||
|
})
|
||||||
|
unsubDeviceConnect = leFuService.onDeviceConnect(() => {
|
||||||
|
this.setData({ bleStatus: 'connected' })
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 模拟获取 WiFi 列表 */
|
/** 初始化设备信息:读取缓存设备名 + 同步蓝牙连接状态 */
|
||||||
onStartSearch() {
|
_initDeviceInfo() {
|
||||||
if (_searchTimer !== null) { clearTimeout(_searchTimer); _searchTimer = null }
|
const cached = wx.getStorageSync('connectDeviceInfo') as Record<string, any> | null
|
||||||
|
const deviceName = cached?.scaleDeviceName || cached?.name
|
||||||
|
if (deviceName) {
|
||||||
|
this.setData({ deviceName })
|
||||||
|
}
|
||||||
|
this.setData({ bleStatus: leFuService.isConnected ? 'connected' : 'failed' })
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 校验设备连接状态:未连接则同步状态 + 提示,返回是否可继续操作 */
|
||||||
|
_ensureConnected(): boolean {
|
||||||
|
if (leFuService.isConnected) return true
|
||||||
|
this.setData({ bleStatus: 'failed' })
|
||||||
|
wx.showToast({ title: '设备已断开,请重新连接', icon: 'none' })
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 通过设备协议获取周围 WiFi 列表 */
|
||||||
|
async onStartSearch() {
|
||||||
|
if (!this._ensureConnected()) {
|
||||||
|
this.setData({ status: 'idle', wifiList: [] })
|
||||||
|
return
|
||||||
|
}
|
||||||
this.setData({ status: 'searching', wifiList: [] })
|
this.setData({ status: 'searching', wifiList: [] })
|
||||||
wx.showLoading({ title: '正在获取 WiFi 列表...', mask: true })
|
wx.showLoading({ title: '正在获取 WiFi 列表...', mask: true })
|
||||||
_searchTimer = setTimeout(() => {
|
try {
|
||||||
|
const list = await leFuService.getWifiList()
|
||||||
wx.hideLoading()
|
wx.hideLoading()
|
||||||
|
if (!list.length) {
|
||||||
|
this.setData({ status: 'empty', wifiList: [] })
|
||||||
|
return
|
||||||
|
}
|
||||||
this.setData({
|
this.setData({
|
||||||
status: 'list',
|
status: 'list',
|
||||||
wifiList: MOCK_WIFI.map(item => ({ ...item })),
|
wifiList: list.map(item => ({ ssid: item.ssid, connected: false })),
|
||||||
})
|
})
|
||||||
}, 1200)
|
} catch {
|
||||||
|
wx.hideLoading()
|
||||||
|
this.setData({ status: 'empty', wifiList: [] })
|
||||||
|
wx.showToast({ title: '获取 WiFi 列表失败', icon: 'none' })
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onRefresh() {
|
onRefresh() {
|
||||||
@@ -62,6 +101,7 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onTapWifi(e: WechatMiniprogram.TouchEvent) {
|
onTapWifi(e: WechatMiniprogram.TouchEvent) {
|
||||||
|
if (!this._ensureConnected()) return
|
||||||
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
|
||||||
@@ -80,13 +120,15 @@ Page({
|
|||||||
this.setData({ status: 'list', selectedSsid: '', selectWifiPWD: '' })
|
this.setData({ status: 'list', selectedSsid: '', selectWifiPWD: '' })
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 模拟配网 */
|
/** 下发配网指令,成功后标记对应 WiFi 已连接 */
|
||||||
onConfirmPwd() {
|
async onConfirmPwd() {
|
||||||
|
if (!this._ensureConnected()) return
|
||||||
const { selectedSsid, selectWifiPWD } = this.data
|
const { selectedSsid, selectWifiPWD } = this.data
|
||||||
if (!selectedSsid) return
|
if (!selectedSsid) return
|
||||||
this.setData({ status: 'configuring' })
|
this.setData({ status: 'configuring' })
|
||||||
wx.showLoading({ title: '正在配网...', mask: true })
|
wx.showLoading({ title: '正在配网...', mask: true })
|
||||||
_configTimer = setTimeout(() => {
|
try {
|
||||||
|
await leFuService.configWifi(selectedSsid, selectWifiPWD)
|
||||||
wx.hideLoading()
|
wx.hideLoading()
|
||||||
this.setData({
|
this.setData({
|
||||||
status: 'list',
|
status: 'list',
|
||||||
@@ -95,11 +137,21 @@ Page({
|
|||||||
selectedSsid: '',
|
selectedSsid: '',
|
||||||
selectWifiPWD: '',
|
selectWifiPWD: '',
|
||||||
})
|
})
|
||||||
}, 1500)
|
wx.showToast({ title: '配网成功', icon: 'success' })
|
||||||
|
} catch (err: any) {
|
||||||
|
wx.hideLoading()
|
||||||
|
this.setData({ selectedSsid: '', selectWifiPWD: '' })
|
||||||
|
wx.showToast({ title: err?.message || '配网失败', icon: 'none' })
|
||||||
|
// 配网失败:延时 1.5s 后清空数据并重新获取 WiFi 列表
|
||||||
|
setTimeout(() => {
|
||||||
|
this.onStartSearch()
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onConfirm() {
|
onConfirm() {
|
||||||
if (!this.data.hasConnected) return
|
if (!this.data.hasConnected) return
|
||||||
wx.navigateTo({ url: '/pages/supplementPersonal/supplementPersonal' })
|
if (!this._ensureConnected()) return
|
||||||
|
wx.navigateTo({ url: '/pages/equipment/equipment' })
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user