Files
2026-05-24 19:03:37 +08:00

426 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RS485 数据上报中断处理方案
## 一、问题描述
项目运行过程中,RS485 总线上报的温湿度、压差、门状态等传感器数据可能中断。当前代码中**没有**数据中断检测、超时告警和自动重连机制。需要增加心跳监听、中断检测和自动重置功能。
---
## 二、涉及文件
| 文件路径 | 修改说明 |
|----------|----------|
| `pages/start.vue` | 新增心跳检测、中断监听和自动重置逻辑 |
---
## 三、修改内容
### 3.1 `data()` — 新增字段
`data()` 的 return 对象中新增四个字段:
**修改位置**`pages/start.vue` 第 51~59 行
**修改前**
```javascript
data() {
return {
clickTimes: 0,
RS485: undefined,
RS232: undefined,
timer: null, // 定时器读取485接口数据
taskTimer: null,
leftDoorCycleTimer: null, // 左门循环定时器
}
},
```
**修改后**
```javascript
data() {
return {
clickTimes: 0,
RS485: undefined,
RS232: undefined,
timer: null, // 定时器读取485接口数据
taskTimer: null,
leftDoorCycleTimer: null, // 左门循环定时器
last485DataTime: null, // 上次收到485数据的时间戳
heartbeatTimer: null, // 心跳检测定时器
reconnectCount: 0, // 重连次数计数器
maxReconnect: 3, // 最大重连次数
}
},
```
---
### 3.2 `handle485HexData()` — 记录数据时间戳
**修改位置**`pages/start.vue` 第 393 行
`handle485HexData(hex)` 方法**开头**增加时间戳记录:
```javascript
async handle485HexData(hex) {
// 更新最后收到数据的时间戳
this.last485DataTime = Date.now()
// 处理485接口上报的数据
// 温湿度temp, humi, 压差pressure, 门状态door
let data = cmd.parse485Data(hex)
// ... 后续所有原有代码保持不变 ...
```
---
### 3.3 `initSerialport()` — 启动心跳检测
**修改位置**`pages/start.vue` 第 159~170 行
**修改前**
```javascript
initSerialport() {
try {
this.startRS485()
this.startRS232()
} catch (e) {
uni.showToast({
title: '初始化串口失败',
icon: 'error'
})
}
},
```
**修改后**
```javascript
initSerialport() {
try {
this.startRS485()
this.startRS232()
// 启动心跳检测
this.startHeartbeat()
} catch (e) {
uni.showToast({
title: '初始化串口失败',
icon: 'error'
})
}
},
```
---
### 3.4 `methods` — 新增三个方法
`methods` 对象中新增以下三个方法,建议放在 `readData()` 方法(第 386 行)**之前**:
#### 3.4.1 `startHeartbeat()` — 启动心跳检测
```javascript
// 启动心跳检测定时器
startHeartbeat() {
// 清除已有的心跳定时器
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = null
}
// 记录日志
this.addLog('485通信', '心跳检测已启动')
// 每10秒检查一次485数据是否正常上报
this.heartbeatTimer = setInterval(() => {
this.check485Heartbeat()
}, 10000)
},
```
#### 3.4.2 `check485Heartbeat()` — 心跳检测逻辑
```javascript
// 检测485数据心跳,如果超时则触发重连
check485Heartbeat() {
// 如果还没有收到过数据,跳过检查(等待初始化完成)
if (!this.last485DataTime) {
return
}
const now = Date.now()
const timeout = 30 * 1000 // 30秒超时阈值
// 判断距离上次数据是否超过30秒
if (now - this.last485DataTime > timeout) {
// 检查重连次数是否超限
if (this.reconnectCount >= this.maxReconnect) {
this.addLog('485通信', '重连' + this.maxReconnect + '次失败,请检查硬件')
uni.showToast({
title: '485通信中断,请检查硬件连接',
icon: 'none',
duration: 3000
})
return
}
// 记录日志
this.addLog('485通信', '数据中断(第' + (this.reconnectCount + 1) + '次重连)')
uni.showToast({
title: '485通信中断,正在恢复...',
icon: 'none',
duration: 2000
})
// 重置监听
this.resetRS485()
}
},
```
#### 3.4.3 `resetRS485()` — 重置RS485监听
```javascript
// 重置RS485监听:停止旧连接 → 重启新连接 → 恢复心跳
async resetRS485() {
try {
this.reconnectCount++
// 1. 暂停心跳检测(避免重置过程中重复触发)
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = null
}
// 2. 停止并关闭当前RS485串口
if (this.RS485) {
this.stopRS485()
// 等待一段时间确保串口完全关闭
await delay(1500)
}
// 3. 清除轮询定时器
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
// 4. 重置时间戳
this.last485DataTime = null
// 5. 重新启动RS485通信
this.startRS485()
// 6. 延迟后重新启动心跳检测(等待RS485初始化完成)
await delay(3000)
this.startHeartbeat()
this.addLog('485通信', '监听已重置(第' + this.reconnectCount + '次)')
} catch (error) {
this.addLog('485通信错误', error.message || '重置失败')
// 失败后延迟重试
await delay(5000)
if (this.reconnectCount < this.maxReconnect) {
this.resetRS485()
}
}
},
```
---
### 3.5 `startRS485()` — 成功后重置计数器和记录日志
**修改位置**`pages/start.vue` 第 318~348 行
`startRS485()` 方法的 `state` 判断块中,新增计数器重置和通信恢复日志:
```javascript
startRS485() {
// 获取本地串口存储端口
let rs485Conf = storage.get('rs485')
if (rs485Conf) {
// 设置串口和波特率
} else {
rs485Conf = {
path: '/dev/ttyUSB2',
baudrate: 9600
}
}
this.RS485.setPath(rs485Conf.path)
this.RS485.setBaudrate(rs485Conf.baudrate)
// 打开串口
const state = this.RS485.open();
if (state) {
uni.showToast({
title: '485已打开',
icon: 'none'
})
this.RS485.startAutoReadData((res) =>{
let hex = this.RS485.byte2HexString(res)
this.handle485HexData(hex)
})
// 读取数据
clearInterval(this.timer)
this.readData()
// 【新增】串口打开成功后,重置重连计数器和时间戳
this.reconnectCount = 0
this.last485DataTime = Date.now()
// 【新增】记录通信恢复日志
this.addLog('485通信', '通信已恢复(重连成功)')
}
},
```
---
### 3.6 `stopRS485()` — 添加日志
**修改位置**`pages/start.vue` 第 377~380 行
**修改前**
```javascript
stopRS485() {
this.RS485.stopReadPortData()
this.RS485.close()
},
```
**修改后**
```javascript
stopRS485() {
this.addLog('485通信', '串口关闭')
this.RS485.stopReadPortData()
this.RS485.close()
},
```
---
### 3.7 页面销毁时清理定时器
**修改位置**`pages/start.vue`,在 `onHide()` 生命周期之后、`methods` 之前添加 `onUnload`
```javascript
onUnload() {
// 记录日志
this.addLog('485通信', '页面销毁,已停止所有定时器')
// 清理心跳定时器
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = null
}
// 清理485轮询定时器
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
// 清理任务定时器
if (this.taskTimer) {
clearInterval(this.taskTimer)
this.taskTimer = null
}
// 清理左门循环定时器
if (this.leftDoorCycleTimer) {
clearInterval(this.leftDoorCycleTimer)
this.leftDoorCycleTimer = null
}
},
```
---
## 四、工作流程图
```
启动RS485 ──▶ startHeartbeat() ──▶ 每10秒检查
┌───────────────────▼────────────────────┐
│ last485DataTime 距现在超过 30 秒? │
└───┬───────────────────────┬────────────┘
│ 否 │ 是
▼ ▼
继续等待 检查 reconnectCount
┌───────────────┴───────────────┐
│ < 3 │ >= 3
▼ ▼
resetRS485() 弹窗告警,停止重连
│ "请检查硬件连接"
┌───────────┼───────────┐
▼ ▼ ▼
stopRS485 重置时间戳 startRS485
│ │
▼ ▼
关闭串口 重新打开串口监听
reconnectCount=0
last485DataTime重置
startHeartbeat()
```
---
## 五、关键参数说明
| 参数 | 值 | 说明 |
|------|-----|------|
| 心跳检测间隔 | 10 秒 | `setInterval` 的执行间隔 |
| 数据超时阈值 | 30 秒 | 超过此时间未收到数据触发重连 |
| 最大重连次数 | 3 次 | 超过后停止自动重连,需手动干预 |
| 重置等待时间 | 1500ms | 关闭串口后等待的时间 |
| 重连初始化等待 | 3000ms | 重新打开串口后等待稳定 |
> 这些参数可根据实际硬件响应速度调整。
---
## 六、日志记录说明
所有关键操作都会记录到 SQLite 数据库的 `log` 表中,方便后续排查问题。
### 6.1 日志表示例
| 时间 | name | action |
|------|------|--------|
| 14:30:00 | 485通信 | 心跳检测已启动 |
| 14:35:30 | 485通信 | 数据中断(第1次重连) |
| 14:35:35 | 485通信 | 监听已重置(第1次) |
| 14:36:00 | 485通信 | 数据中断(第2次重连) |
| 14:36:05 | 485通信 | 监听已重置(第2次) |
| 14:36:35 | 485通信 | 重连3次失败,请检查硬件 |
| 15:00:00 | 485通信 | 通信已恢复(重连成功) |
| 15:30:00 | 485通信 | 页面销毁,已停止所有定时器 |
### 6.2 日志查看方式
日志存储在本地 SQLite 数据库的 `log` 表中,可以通过"sqlite调试"页面查看。
---
## 七、测试验证
1. **正常情况**:启动后每5秒通过 `readData` 主动查询,传感器定时上报,`last485DataTime` 持续更新,心跳检测通过
2. **模拟中断**:断开485传感器线缆,确认30秒后触发重连提示,3次后弹窗告警
3. **模拟恢复**:在重连过程中重新接入传感器,确认 `startRS485` 成功后将计数器归零
4. **页面切换**:离开 `start.vue` 时确认所有定时器已清除
5. **日志验证**:检查 SQLite 数据库中的 `log` 表,确认所有关键操作都有记录
---
## 八、注意事项
1. **不需要控制台输出**:所有日志都记录到数据库,方便在生产环境排查问题
2. **重连失败不需要手动恢复**:超过最大重连次数后,只能重启应用或检查硬件
3. **参数调整**:如果硬件响应较慢,可以适当增加超时阈值和等待时间
4. **左门循环定时器**:这是独立的调试功能,不受485中断处理影响