优化微信小程序 持续接收雷达状态后卡住问题
This commit is contained in:
@@ -38,6 +38,8 @@ Page({
|
||||
// 条件“有无人标记”选项,参考截图:无人至有人/短暂离开/长时间离开/有人至无人
|
||||
tagOptions: ['无人至有人', '短暂离开', '长时间离开', '有人至无人'],
|
||||
stateOptions: ['不判断', '触发', '释放', '关至开', '开至关'],
|
||||
// 门磁专用选项(与其他端口不同):0=不判断,1=关门,2=开门,3=关至开,4=开至关
|
||||
doorMagOptions: ['不判断', '关门', '开门', '关至开', '开至关'],
|
||||
// 默认条件:由用户指定的条件组与条件
|
||||
conditions: [
|
||||
// 组1: timeout 2秒,包含1个条件
|
||||
@@ -49,11 +51,11 @@ Page({
|
||||
// 组3: timeout 2秒,包含1个条件
|
||||
{ group: 3, seq: 1, tag: 1, cardPower: 0, doorMag: 4, irHall: 0, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 0, timeout: 2, timeoutUnit: 0 },
|
||||
// 组4: timeout 10分,包含1个条件
|
||||
{ group: 4, seq: 1, tag: 1, cardPower: 0, doorMag: 3, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 2, judgeUnit: 1, timeout: 10, timeoutUnit: 1 },
|
||||
{ group: 4, seq: 1, tag: 1, cardPower: 0, doorMag: 1, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 2, judgeUnit: 1, timeout: 10, timeoutUnit: 1 },
|
||||
// 组5: timeout 10分,包含1个条件
|
||||
{ group: 5, seq: 1, tag: 2, cardPower: 0, doorMag: 3, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 2, judgeUnit: 1, timeout: 10, timeoutUnit: 1 },
|
||||
{ group: 5, seq: 1, tag: 2, cardPower: 0, doorMag: 1, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 2, judgeUnit: 1, timeout: 10, timeoutUnit: 1 },
|
||||
// 组6: timeout 10分,包含1个条件
|
||||
{ group: 6, seq: 1, tag: 3, cardPower: 0, doorMag: 3, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 2, judgeUnit: 1, timeout: 10, timeoutUnit: 1 }
|
||||
{ group: 6, seq: 1, tag: 3, cardPower: 0, doorMag: 1, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 2, judgeUnit: 1, timeout: 10, timeoutUnit: 1 }
|
||||
],
|
||||
// 二级菜单:按组折叠
|
||||
condGroups: [],
|
||||
@@ -78,7 +80,7 @@ Page({
|
||||
// 门磁/卫浴事件控件数据(仅保留 秒 / 分)
|
||||
eventUnits: ['秒', '分'],
|
||||
// 雷达读取状态:false=未读取, true=正在读取
|
||||
radarReading: false,
|
||||
radarReading: true,
|
||||
// 门磁
|
||||
doorTriggerDelay: 0,
|
||||
doorTriggerUnitIndex: 0,
|
||||
@@ -249,6 +251,13 @@ Page({
|
||||
if (id === 2) {
|
||||
this.onSendReadVersion()
|
||||
}
|
||||
// 切换到设备升级标签(data-id === 3)时,默认发送一包 OTA 升级命令
|
||||
if (id === 3) {
|
||||
// 给用户一点缓冲时间再发送(确保 UI 切换完成)
|
||||
// setTimeout(() => {
|
||||
// this.onStartOta()
|
||||
// }, 200)
|
||||
}
|
||||
},
|
||||
|
||||
onOpenDelayChange(e) {
|
||||
@@ -294,9 +303,10 @@ Page({
|
||||
// 通过 picker index 推导真实数值(index + 1)
|
||||
const opts = {
|
||||
door: {
|
||||
triggerDelay: Number((d.doorTriggerIndex || 0) + 1),
|
||||
// use the actual delay value (can be 0) instead of index+1
|
||||
triggerDelay: Number(d.doorTriggerDelay || 0),
|
||||
triggerUnit: mapUnit(d.doorTriggerUnitIndex),
|
||||
releaseDelay: Number((d.doorReleaseIndex || 0) + 1),
|
||||
releaseDelay: Number(d.doorReleaseDelay || 0),
|
||||
releaseUnit: mapUnit(d.doorReleaseUnitIndex)
|
||||
},
|
||||
bath: null
|
||||
@@ -344,9 +354,9 @@ Page({
|
||||
const opts = {
|
||||
door: null,
|
||||
bath: {
|
||||
triggerDelay: Number((d.bathTriggerIndex || 0) + 1),
|
||||
triggerDelay: Number(d.bathTriggerDelay || 0),
|
||||
triggerUnit: mapUnit(d.bathTriggerUnitIndex),
|
||||
releaseDelay: Number((d.bathReleaseIndex || 0) + 1),
|
||||
releaseDelay: Number(d.bathReleaseDelay || 0),
|
||||
releaseUnit: mapUnit(d.bathReleaseUnitIndex)
|
||||
}
|
||||
}
|
||||
@@ -851,7 +861,7 @@ Page({
|
||||
// 开启订阅(若已传入rx特征),保证能接收数据
|
||||
this.enableNotify()
|
||||
this.setupBleListener()
|
||||
// this.sendRadarStatusCommand(true)
|
||||
this.sendRadarStatusCommand(true)
|
||||
},
|
||||
|
||||
enableNotify() {
|
||||
@@ -1033,6 +1043,7 @@ Page({
|
||||
*/
|
||||
setupBleListener() {
|
||||
// 移除旧监听,防止重复触发(页面重复进入或多次初始化的场景)
|
||||
|
||||
this.teardownBleListener()
|
||||
// 运行环境不支持通知回调则直接返回(避免报错)
|
||||
if (typeof wx.onBLECharacteristicValueChange !== 'function') return
|
||||
@@ -1073,7 +1084,23 @@ Page({
|
||||
// 注册系统 BLE 通知回调
|
||||
wx.onBLECharacteristicValueChange(this._onBleChange)
|
||||
},
|
||||
|
||||
onDownloadOtaTool() {
|
||||
const url = 'https://www.baidu.com/';
|
||||
if (wx && wx.setClipboardData) {
|
||||
wx.setClipboardData({
|
||||
data: url,
|
||||
success() {
|
||||
wx.showToast({ title: '下载链接已复制到剪贴板', icon: 'none' });
|
||||
},
|
||||
fail() {
|
||||
wx.showModal({ title: '提示', content: '无法复制链接,请手动访问:' + url, showCancel: false });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// fallback
|
||||
wx.showModal({ title: '提示', content: '下载链接:' + url, showCancel: false });
|
||||
}
|
||||
},
|
||||
teardownBleListener() {
|
||||
if (this._onBleChange && typeof wx.offBLECharacteristicValueChange === 'function') {
|
||||
wx.offBLECharacteristicValueChange(this._onBleChange)
|
||||
@@ -1145,39 +1172,61 @@ Page({
|
||||
},
|
||||
|
||||
updateRadarLights(bits) {
|
||||
// 更新雷达指示灯数组(保持原有展示)
|
||||
// 节流更新:合并高频上报,避免频繁 setData 导致 UI 卡顿
|
||||
try {
|
||||
const now = Date.now()
|
||||
const throttleMs = 200
|
||||
if (!this._lastRadarUpdateTime || (now - this._lastRadarUpdateTime) >= throttleMs) {
|
||||
// 直接更新
|
||||
this._lastRadarUpdateTime = now
|
||||
const next = (this.data.radarLights || []).map((it, idx) => {
|
||||
const triggered = ((bits >> idx) & 0x01) === 1
|
||||
return {
|
||||
...it,
|
||||
triggered,
|
||||
colorClass: triggered ? 'red' : 'green'
|
||||
}
|
||||
return Object.assign({}, it, { triggered, colorClass: triggered ? 'red' : 'green' })
|
||||
})
|
||||
|
||||
// 解析位值(约定:bit0=门磁, bit1=卫浴, bit2=卧室, bit3=走廊)
|
||||
const doorTriggered = ((bits >> 0) & 0x01) === 1
|
||||
const bathTriggered = ((bits >> 1) & 0x01) === 1
|
||||
const bedTriggered = ((bits >> 2) & 0x01) === 1
|
||||
const hallTriggered = ((bits >> 3) & 0x01) === 1
|
||||
|
||||
// 计算卡片显示规则:默认灰色,按需求将对应卡片设色
|
||||
const cardClasses = {
|
||||
// 房间有人:卧室或走廊任一触发 -> 橙色,否则灰色
|
||||
roomHasPeople: (bedTriggered || hallTriggered) ? 'orange' : 'gray',
|
||||
// 房间无人:卧室和走廊均处于释放 -> 绿色,否则灰色
|
||||
roomNoPeople: (!bedTriggered && !hallTriggered) ? 'green' : 'gray',
|
||||
// 门开:门磁释放 -> 红色,否则灰色
|
||||
doorOpen: (!doorTriggered) ? 'red' : 'gray',
|
||||
// 门关:门磁触发 -> 绿色,否则灰色
|
||||
doorClose: (doorTriggered) ? 'green' : 'gray',
|
||||
// 卫浴有人:卫浴触发 -> 蓝色,否则灰色
|
||||
bathHasPeople: (bathTriggered) ? 'blue' : 'gray',
|
||||
// 卫浴无人:卫浴释放 -> 绿色,否则灰色
|
||||
bathNoPeople: (!bathTriggered) ? 'green' : 'gray'
|
||||
}
|
||||
|
||||
this.setData({ radarLights: next, cardClasses })
|
||||
} else {
|
||||
// 延迟更新到节流窗口结束,使用最新 bits
|
||||
this._pendingRadarBits = bits
|
||||
if (this._radarUpdateTimer) clearTimeout(this._radarUpdateTimer)
|
||||
this._radarUpdateTimer = setTimeout(() => {
|
||||
try {
|
||||
const b = this._pendingRadarBits || 0
|
||||
this._lastRadarUpdateTime = Date.now()
|
||||
const next = (this.data.radarLights || []).map((it, idx) => {
|
||||
const triggered = ((b >> idx) & 0x01) === 1
|
||||
return Object.assign({}, it, { triggered, colorClass: triggered ? 'red' : 'green' })
|
||||
})
|
||||
const doorTriggered = ((b >> 0) & 0x01) === 1
|
||||
const bathTriggered = ((b >> 1) & 0x01) === 1
|
||||
const bedTriggered = ((b >> 2) & 0x01) === 1
|
||||
const hallTriggered = ((b >> 3) & 0x01) === 1
|
||||
const cardClasses = {
|
||||
roomHasPeople: (bedTriggered || hallTriggered) ? 'orange' : 'gray',
|
||||
roomNoPeople: (!bedTriggered && !hallTriggered) ? 'green' : 'gray',
|
||||
doorOpen: (!doorTriggered) ? 'red' : 'gray',
|
||||
doorClose: (doorTriggered) ? 'green' : 'gray',
|
||||
bathHasPeople: (bathTriggered) ? 'blue' : 'gray',
|
||||
bathNoPeople: (!bathTriggered) ? 'green' : 'gray'
|
||||
}
|
||||
this.setData({ radarLights: next, cardClasses })
|
||||
} catch (e) { /* ignore */ }
|
||||
}, throttleMs)
|
||||
}
|
||||
} catch (e) {
|
||||
try { console.warn('updateRadarLights error', e) } catch (xx) { /* ignore */ }
|
||||
}
|
||||
},
|
||||
|
||||
// 数值约束
|
||||
@@ -1400,6 +1449,94 @@ Page({
|
||||
wx.showToast({ title: '端口配置已发送', icon: 'success' })
|
||||
},
|
||||
|
||||
// 仅下发端口配置(参考一键下发,但只发送 0x09 端口包)
|
||||
onSendPorts() {
|
||||
const sendPorts = async () => {
|
||||
try {
|
||||
this.appendLog('UI', '操作: 端口下发(开始)')
|
||||
// 标准化 ports:缺失 deviceType/deviceAddr 时写入默认值
|
||||
try {
|
||||
const rawPorts = Array.isArray(this.data.ports) ? this.data.ports : []
|
||||
let changed = false
|
||||
const normalized = rawPorts.map((p) => {
|
||||
const np = { ...p }
|
||||
if (np.deviceType == null || np.deviceType === 0) {
|
||||
if (np.deviceType !== 2) { np.deviceType = 2; changed = true }
|
||||
}
|
||||
if (np.deviceAddr == null || np.deviceAddr === 0) {
|
||||
if (np.deviceAddr !== 1) { np.deviceAddr = 1; changed = true }
|
||||
}
|
||||
return np
|
||||
})
|
||||
if (changed) {
|
||||
try { this.setData({ ports: normalized }) } catch (e) { /* ignore */ }
|
||||
}
|
||||
} catch (e) { /* ignore normalization error */ }
|
||||
|
||||
const ports = Array.isArray(this.data.ports) ? this.data.ports : []
|
||||
const portPkts = []
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
const p = ports[i]
|
||||
const P0 = (p && p.deviceType) ? (p.deviceType & 0xFF) : 0
|
||||
const P1 = (p && p.deviceAddr) ? (p.deviceAddr & 0xFF) : 0
|
||||
const loopLE = [((p && p.loop) || 0) & 0xFF, (((p && p.loop) || 0) >>> 8) & 0xFF]
|
||||
const P4 = ((p && p.thresholdDown) || 0) & 0xFF
|
||||
const P5 = this.getVirtualPort(p, i)
|
||||
const P6 = (p && p.enabled) ? 0x01 : 0x00
|
||||
const dtLE = [((p && p.detectTime) || 0) & 0xFF, ((((p && p.detectTime) || 0) >>> 8) & 0xFF)]
|
||||
const P9 = (this.normalizeUnitForPacket ? this.normalizeUnitForPacket((p && p.detectUnit) || 0) : this.unitIndexToProtocolValue((p && p.detectUnit) || 0)) & 0xFF
|
||||
const P10 = ((p && p.thresholdUp) || 0) & 0xFF
|
||||
const payload = [P0, P1, ...loopLE, P4, P5, P6, ...dtLE, P9, P10]
|
||||
const pkt = buildCommand(COMMANDS.SET_CONDITION_2, payload, { frame: i + 1, framNum: ports.length })
|
||||
portPkts.push({ pkt, label: `端口 ${p && p.name || (i+1)}` })
|
||||
this.appendLog('TX', `端口配置[${p && p.name || (i+1)}]: ${this.toHex(pkt)}`)
|
||||
}
|
||||
|
||||
try { this.enableNotify() } catch (e) { /* ignore */ }
|
||||
|
||||
const sendWithRetry = async (pktObj, cmdType) => {
|
||||
const maxRetries = 3
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const waitPromise = this.waitForResponse(cmdType, 3000)
|
||||
try { await this.writeRawBytes(pktObj.pkt, pktObj.label) } catch (writeErr) {
|
||||
if (this._pendingResponse && typeof this._pendingResponse.reject === 'function') {
|
||||
try { this._pendingResponse.reject(new Error('写入失败')) } catch (e) { /* ignore */ }
|
||||
this._pendingResponse = null
|
||||
}
|
||||
throw writeErr
|
||||
}
|
||||
const params = await waitPromise
|
||||
if (params && params.length > 0 && (params[0] & 0xFF) === 0x01) {
|
||||
this.appendLog('UI', `${pktObj.label} 下发成功 (attempt ${attempt})`)
|
||||
await new Promise(r => setTimeout(r, 100))
|
||||
return true
|
||||
} else {
|
||||
throw new Error('设备返回失败或参数不正确')
|
||||
}
|
||||
} catch (err) {
|
||||
this.appendLog('WARN', `${pktObj.label} 发送 attempt ${attempt} 失败: ${err && (err.message || err)}`)
|
||||
if (attempt < maxRetries) { await new Promise(r => setTimeout(r, 200)); continue }
|
||||
throw new Error(`${pktObj.label} 下发失败`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < portPkts.length; i++) {
|
||||
const obj = portPkts[i]
|
||||
await sendWithRetry(obj, COMMANDS.SET_CONDITION_2 & 0xFF)
|
||||
}
|
||||
|
||||
wx.showToast({ title: '端口下发完成', icon: 'success' })
|
||||
this.appendLog('UI', '端口下发完成')
|
||||
} catch (err) {
|
||||
wx.showToast({ title: '端口下发失败', icon: 'none' })
|
||||
this.appendLog('WARN', `端口下发失败: ${err && (err.message || err)}`)
|
||||
}
|
||||
}
|
||||
sendPorts()
|
||||
},
|
||||
|
||||
// 读取端口配置(占位示例,后续可接入真实读取命令)
|
||||
onReadPorts() {
|
||||
this.appendLog('UI', '请求读取端口配置')
|
||||
@@ -1473,6 +1610,89 @@ Page({
|
||||
wx.showToast({ title: '条件配置已发送', icon: 'success' })
|
||||
},
|
||||
|
||||
// 仅下发条件配置(参考一键下发,但只发送 0x08 条件包)
|
||||
onSendConditions() {
|
||||
const sendConds = async () => {
|
||||
try {
|
||||
this.appendLog('UI', '操作: 条件下发(开始)')
|
||||
// 扁平化 condGroups 并兼容旧字段名
|
||||
const flat = []
|
||||
;(this.data.condGroups || []).forEach(grp => {
|
||||
(grp.items || []).forEach(it => {
|
||||
const item = { ...it, group: grp.group, timeout: grp.timeout, timeoutUnit: grp.timeoutUnit }
|
||||
if (item.tag == null && (item.cardPower != null)) item.tag = item.cardPower
|
||||
if (item.judgeUnit == null) item.judgeUnit = (item.judgeUnit === 0 ? 0 : 2)
|
||||
if (item.timeoutUnit == null) item.timeoutUnit = (item.timeoutUnit === 0 ? 0 : 2)
|
||||
flat.push(item)
|
||||
})
|
||||
})
|
||||
|
||||
const condPkts = []
|
||||
for (let i = 0; i < flat.length; i++) {
|
||||
const c = flat[i]
|
||||
const P0 = (typeof this.tagIndexToProtocolValue === 'function' ? this.tagIndexToProtocolValue((c && c.tag) || 0) : (((c && c.tag) || 0) + 1)) & 0xFF
|
||||
const P1 = ((c && c.group) || 0) & 0xFF
|
||||
const P2 = ((c && c.seq) || 0) & 0xFF
|
||||
const jtLE = [((c && c.judgeTime) || 0) & 0xFF, (((c && c.judgeTime) || 0) >>> 8) & 0xFF]
|
||||
const P5 = (typeof this.normalizeUnitForPacket === 'function') ? (this.normalizeUnitForPacket((c && c.judgeUnit) || 0) & 0xFF) : (((c && c.judgeUnit) || 0) & 0xFF)
|
||||
const P6 = ((c && c.cardPower) || 0) & 0xFF
|
||||
const P7 = ((c && c.doorMag) || 0) & 0xFF
|
||||
const P8 = ((c && c.bathRadar) || 0) & 0xFF
|
||||
const P9 = ((c && c.bathroomRadar) || 0) & 0xFF
|
||||
const P10 = ((c && c.irHall) || 0) & 0xFF
|
||||
const toLE = [((c && c.timeout) || 0) & 0xFF, ((((c && c.timeout) || 0) >>> 8) & 0xFF)]
|
||||
const P13 = (typeof this.normalizeUnitForPacket === 'function') ? (this.normalizeUnitForPacket((c && c.timeoutUnit) || 0) & 0xFF) : (((c && c.timeoutUnit) || 0) & 0xFF)
|
||||
const payload = [P0, P1, P2, ...jtLE, P5, P6, P7, P8, P9, P10, ...toLE, P13]
|
||||
const pkt = buildCommand(COMMANDS.SET_CONDITION_1, payload, { frame: i + 1, framNum: flat.length })
|
||||
condPkts.push({ pkt, label: `条件 ${c && c.group || ''}-${c && c.seq || i+1}` })
|
||||
this.appendLog('TX', `条件配置[组${c.group}/序${c.seq}]: ${this.toHex(pkt)}`)
|
||||
}
|
||||
|
||||
try { this.enableNotify() } catch (e) { /* ignore */ }
|
||||
|
||||
const sendWithRetry = async (pktObj, cmdType) => {
|
||||
const maxRetries = 3
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const waitPromise = this.waitForResponse(cmdType, 3000)
|
||||
try { await this.writeRawBytes(pktObj.pkt, pktObj.label) } catch (writeErr) {
|
||||
if (this._pendingResponse && typeof this._pendingResponse.reject === 'function') {
|
||||
try { this._pendingResponse.reject(new Error('写入失败')) } catch (e) { /* ignore */ }
|
||||
this._pendingResponse = null
|
||||
}
|
||||
throw writeErr
|
||||
}
|
||||
const params = await waitPromise
|
||||
if (params && params.length > 0 && (params[0] & 0xFF) === 0x01) {
|
||||
this.appendLog('UI', `${pktObj.label} 下发成功 (attempt ${attempt})`)
|
||||
await new Promise(r => setTimeout(r, 100))
|
||||
return true
|
||||
} else {
|
||||
throw new Error('设备返回失败或参数不正确')
|
||||
}
|
||||
} catch (err) {
|
||||
this.appendLog('WARN', `${pktObj.label} 发送 attempt ${attempt} 失败: ${err && (err.message || err)}`)
|
||||
if (attempt < maxRetries) { await new Promise(r => setTimeout(r, 200)); continue }
|
||||
throw new Error(`${pktObj.label} 下发失败`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < condPkts.length; i++) {
|
||||
const obj = condPkts[i]
|
||||
await sendWithRetry(obj, COMMANDS.SET_CONDITION_1 & 0xFF)
|
||||
}
|
||||
|
||||
wx.showToast({ title: '条件下发完成', icon: 'success' })
|
||||
this.appendLog('UI', '条件下发完成')
|
||||
} catch (err) {
|
||||
wx.showToast({ title: '条件下发失败', icon: 'none' })
|
||||
this.appendLog('WARN', `条件下发失败: ${err && (err.message || err)}`)
|
||||
}
|
||||
}
|
||||
sendConds()
|
||||
},
|
||||
|
||||
// 功能栏:删除条件组
|
||||
onDeleteCondGroup() {
|
||||
const groups = (this.data.condGroups || []).map(g => String(g.group))
|
||||
@@ -2280,8 +2500,16 @@ Page({
|
||||
} catch (e) { /* ignore console errors */ }
|
||||
|
||||
const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||
const next = [{ id, time: timeStr, text: finalText }, ...this.data.logList]
|
||||
|
||||
// 限制日志长度,避免无限增长导致 setData 负担和内存问题(保留最新200条)
|
||||
try {
|
||||
const maxLogs = 200
|
||||
const cur = Array.isArray(this.data.logList) ? this.data.logList : []
|
||||
const next = [{ id, time: timeStr, text: finalText }, ...cur]
|
||||
if (next.length > maxLogs) next.length = maxLogs
|
||||
this.setData({ logList: next })
|
||||
} catch (e) {
|
||||
// 若 setData 出现异常,仍保证不抛到外层
|
||||
try { console.warn('appendLog setData failed', e) } catch (xx) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -9,23 +9,21 @@
|
||||
<!-- 顶部标签:参考主机升级页面的切换方式 -->
|
||||
<scroll-view scroll-x class="bg-white nav text-center text-bold text-xl">
|
||||
<view class="flex text-center">
|
||||
<view class="cu-item {{1==TabCur?'text-blue cur':''}}" bindtap="tabSelect" data-id="1">蓝牙调试</view>
|
||||
<view class="cu-item {{2==TabCur?'text-blue cur':''}}" bindtap="tabSelect" data-id="2">蓝牙升级</view>
|
||||
<view class="cu-item {{1==TabCur?'text-blue cur':''}}" bindtap="tabSelect" data-id="1">设备测试</view>
|
||||
<view class="cu-item {{2==TabCur?'text-blue cur':''}}" bindtap="tabSelect" data-id="2">设备配置</view>
|
||||
<view class="cu-item {{3==TabCur?'text-blue cur':''}}" bindtap="tabSelect" data-id="3">设备升级</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- Tab: 蓝牙调试 -->
|
||||
<!-- Tab: 设备测试 -->
|
||||
<view wx:if="{{TabCur==1}}" class="content">
|
||||
|
||||
|
||||
|
||||
<!-- 顶部操作按钮 -->
|
||||
<!-- 顶部操作按钮
|
||||
<view class="top-actions" style="display:flex; gap:6rpx; padding:4rpx 6rpx;">
|
||||
<button size="mini" type="primary" bindtap="sendDoorEvent">设置门磁延时</button>
|
||||
<button size="mini" type="primary" bindtap="sendBathEvent">设置卫浴延时</button>
|
||||
<button size="mini" type="{{radarReading ? 'warn' : 'primary'}}" bindtap="toggleRadarRead">{{radarReading ? '停止读取' : '读雷达状态'}}</button>
|
||||
<button size="mini" type="primary" bindtap="onStartOta">OTA升级</button>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 顶部设备分类 -->
|
||||
<view class="grid">
|
||||
@@ -80,10 +78,13 @@
|
||||
<view class="cfg-card">
|
||||
<view class="cfg-head head-row">
|
||||
<text>门磁开廊灯事件设置</text>
|
||||
<view class="head-actions">
|
||||
<button size="mini" type="primary" bindtap="sendDoorEvent">设置门磁延时</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="form-inline">
|
||||
<text class="label">触发延迟</text>
|
||||
<text class="label">开门延时</text>
|
||||
<picker class="inline-picker" mode="selector" range="{{doorTriggerOptions}}" value="{{doorTriggerIndex}}" bindchange="onDoorTriggerPickerChange">
|
||||
<view class="picker-text">{{doorTriggerOptions[doorTriggerIndex]}}</view>
|
||||
</picker>
|
||||
@@ -91,7 +92,7 @@
|
||||
<picker class="inline-picker" mode="selector" range="{{eventUnits}}" value="{{doorTriggerUnitIndex}}" bindchange="onDoorUnitChange" data-field="triggerUnit">
|
||||
<view class="picker-text">{{eventUnits[doorTriggerUnitIndex]}}</view>
|
||||
</picker>
|
||||
<text class="label">释放延迟</text>
|
||||
<text class="label">关门延时</text>
|
||||
<picker class="inline-picker" mode="selector" range="{{doorReleaseOptions}}" value="{{doorReleaseIndex}}" bindchange="onDoorReleasePickerChange">
|
||||
<view class="picker-text">{{doorReleaseOptions[doorReleaseIndex]}}</view>
|
||||
</picker>
|
||||
@@ -106,6 +107,9 @@
|
||||
<view class="cfg-card">
|
||||
<view class="cfg-head head-row">
|
||||
<text>卫浴雷达开卫浴灯事件设置</text>
|
||||
<view class="head-actions">
|
||||
<button size="mini" type="primary" bindtap="sendBathEvent">设置卫浴延时</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<view class="form-inline">
|
||||
@@ -133,7 +137,10 @@
|
||||
<view class="log-card">
|
||||
<view class="log-head">
|
||||
<text class="title">通讯日志</text>
|
||||
<text class="action" bindtap="onClearLogs">清空</text>
|
||||
<view class="log-actions">
|
||||
<button class="radar-btn" size="mini" type="{{radarReading ? 'warn' : 'primary'}}" bindtap="toggleRadarRead">{{radarReading ? '停止读取' : '读雷达状态'}}</button>
|
||||
<button class="clear-btn" size="mini" type="default" bindtap="onClearLogs">清空</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="log-options">
|
||||
@@ -168,8 +175,9 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab: 蓝牙升级 -->
|
||||
<!-- Tab: 设备升级 -->
|
||||
<view wx:if="{{TabCur==2}}" class="content">
|
||||
<!-- 设备信息栏 -->
|
||||
<view class="device-row">
|
||||
<view class="device-left">
|
||||
<view class="device-line">
|
||||
@@ -205,6 +213,7 @@
|
||||
<view class="cfg-head head-row">
|
||||
<text>端口配置</text>
|
||||
<view class="head-actions">
|
||||
<button size="mini" type="primary" bindtap="onSendPorts">端口下发</button>
|
||||
<button class="hide-btn" size="mini" type="default" bindtap="onReadPorts">读取配置</button>
|
||||
<button class="hide-btn" size="mini" type="primary" bindtap="onSavePorts">保存配置</button>
|
||||
</view>
|
||||
@@ -252,6 +261,7 @@
|
||||
<view class="cfg-head head-row">
|
||||
<text>条件配置</text>
|
||||
<view class="head-actions">
|
||||
<button size="mini" type="primary" bindtap="onSendConditions">条件下发</button>
|
||||
<button class="hide-btn" size="mini" type="warn" bindtap="onDeleteCondGroup">删除条件组</button>
|
||||
<button class="hide-btn" size="mini" type="warn" bindtap="onDeleteCondition">删除条件</button>
|
||||
<button class="hide-btn" size="mini" type="primary" bindtap="onAddCondGroup">添加条件组</button>
|
||||
@@ -275,7 +285,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="head-actions">
|
||||
<button size="mini" type="default" bindtap="onToggleGroup" data-idx="{{gidx}}">{{grp.expanded?'收起':'展开'}}</button>
|
||||
<button class="group-toggle" size="mini" type="default" bindtap="onToggleGroup" data-idx="{{gidx}}" title="{{grp.expanded ? '收起' : '展开'}}">{{grp.expanded ? '▴' : '▾'}}</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -322,8 +332,8 @@
|
||||
</picker>
|
||||
</view>
|
||||
<view class="td">
|
||||
<picker mode="selector" range="{{stateOptions}}" value="{{it.doorMag}}" bindchange="onItemPickerChange" data-gidx="{{gidx}}" data-iidx="{{iidx}}" data-field="doorMag">
|
||||
<view class="picker-text">{{stateOptions[it.doorMag]}}</view>
|
||||
<picker mode="selector" range="{{doorMagOptions}}" value="{{it.doorMag}}" bindchange="onItemPickerChange" data-gidx="{{gidx}}" data-iidx="{{iidx}}" data-field="doorMag">
|
||||
<view class="picker-text">{{doorMagOptions[it.doorMag]}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="td">
|
||||
@@ -397,5 +407,29 @@
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab: 设备升级 -->
|
||||
<view wx:if="{{TabCur==3}}" class="content">
|
||||
<!-- OTA 升级卡片(顶部) -->
|
||||
<view class="cfg-card ota-card">
|
||||
<view class="cfg-head head-row">
|
||||
<text>OTA 升级工具</text>
|
||||
<view class="head-actions">
|
||||
<button size="mini" type="primary" bindtap="onStartOta">发送 OTA 升级命令</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="ota-steps">
|
||||
<view class="step">1. 升级工具下载:
|
||||
<button class="link-btn" size="mini" bindtap="onDownloadOtaTool">下载工具</button>
|
||||
</view>
|
||||
<view class="step">2. 安装软件</view>
|
||||
<view class="step">3. 点击 OTA 按钮,发送 OTA 升级命令</view>
|
||||
<view class="step">4. 打开下载的 【OTA 升级工具】</view>
|
||||
<view class="step">5. 蓝牙连接名称:<text class="mono">OTAOTA_OTAOTA_OTA</text></view>
|
||||
<view class="step">6. 连接后点击 <text class="mono">GETINFO</text>,再点击 <text class="mono">IMAGEA</text></view>
|
||||
<view class="step">7. 选择升级固件后,点击最下面的 <text class="mono">START</text> 按钮,再选择芯片 “CH573” 开始升级</view>
|
||||
<view class="note">下载链接: https://www.baidu.com/ (点击“下载工具”将复制链接)</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -26,7 +26,7 @@
|
||||
.icon.gray { background: #9aa0a6; }
|
||||
.icon { overflow: hidden; display:flex; align-items:center; justify-content:center; }
|
||||
.icon-img { width: 100%; height: 100%; display: block; }
|
||||
.card-title { font-size: 24rpx; color: #555; }
|
||||
.card-title { font-size: 22rpx; color: #555; }
|
||||
|
||||
/* 按下效果 */
|
||||
.card.pressed { transform: translateY(4rpx) scale(0.985); filter: brightness(0.94); box-shadow: none; }
|
||||
@@ -41,6 +41,28 @@
|
||||
.log-head .title { font-size: 26rpx; color: #333; }
|
||||
.log-head .action { font-size: 24rpx; color: #2bab99; }
|
||||
|
||||
/* 日志头按钮组:读雷达 与 清空 按钮并列样式 */
|
||||
.log-head .log-actions { display:flex; align-items:center; gap: 8rpx; }
|
||||
.log-head .log-actions .radar-btn {
|
||||
padding: 0 16rpx;
|
||||
height: 48rpx;
|
||||
line-height: 48rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.log-head .log-actions .clear-btn {
|
||||
padding: 0 16rpx;
|
||||
min-width: 130rpx;
|
||||
height: 48rpx;
|
||||
line-height: 48rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 22rpx;
|
||||
background: #ffffff !important;
|
||||
color: #2bab99 !important;
|
||||
border: 1rpx solid rgba(43,171,153,0.18) !important;
|
||||
}
|
||||
.log-head .log-actions .clear-btn:active { opacity: 0.9 }
|
||||
|
||||
.log-options { display: flex; flex-wrap: nowrap; gap: 14rpx; }
|
||||
.log-options .option { display: flex; align-items: center; font-size: 24rpx; color: #555; white-space: nowrap; }
|
||||
.log-options checkbox { margin-right: 10rpx; transform: scale(0.9); }
|
||||
@@ -62,6 +84,13 @@
|
||||
.dr-btn-view:active { opacity: 0.85; }
|
||||
|
||||
/* 顶部操作按钮:紧凑内间距,适配工具栏顶部的多个小按钮 */
|
||||
.top-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 6rpx;
|
||||
padding: 4rpx 6rpx;
|
||||
}
|
||||
|
||||
.top-actions button {
|
||||
padding: 0 10rpx;
|
||||
height: 58rpx;
|
||||
@@ -192,6 +221,23 @@
|
||||
.log-text { display: block; font-size: 24rpx; color: #333; word-break: break-all; }
|
||||
.log-empty { font-size: 24rpx; color: #9aa0a6; text-align: center; padding: 20rpx 0; }
|
||||
|
||||
/* group toggle button (triangle) styling */
|
||||
.group-toggle {
|
||||
padding: 0 8rpx !important;
|
||||
width: 52rpx !important;
|
||||
height: 40rpx !important;
|
||||
line-height: 40rpx !important;
|
||||
font-size: 26rpx !important;
|
||||
border-radius: 8rpx !important;
|
||||
background: transparent !important;
|
||||
color: #0ea5e9 !important;
|
||||
border: 1rpx solid rgba(14,165,233,0.14) !important;
|
||||
}
|
||||
.group-toggle:active { background: rgba(14,165,233,0.06) !important; }
|
||||
|
||||
/* 收窄条件组超时输入宽度 */
|
||||
.cond-card .group-time input.picker-text { width: 80rpx; padding: 0 6rpx; box-sizing: border-box; }
|
||||
|
||||
/* Inline form for trigger/release delay - keep controls on one row on mobile */
|
||||
.form-row { display:block; }
|
||||
.form-inline { display:flex; align-items:center; gap: 10rpx; flex-wrap: nowrap; }
|
||||
|
||||
@@ -261,8 +261,7 @@ Page({
|
||||
const withParams = (svc && tx && rx)
|
||||
? `${base}&serviceId=${encodeURIComponent(svc)}&txCharId=${encodeURIComponent(tx)}&rxCharId=${encodeURIComponent(rx)}`
|
||||
: base
|
||||
console.log(url)
|
||||
console.log(withParams)
|
||||
console.log('navigateTo:', withParams)
|
||||
wx.navigateTo({ url: withParams })
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user