532 lines
14 KiB
Markdown
532 lines
14 KiB
Markdown
# Distributed Collector Gateway - 使用说明
|
||
|
||
## 功能概述
|
||
|
||
本设备是一个分布式采集网关,支持通过 MQTT 远程控制 MODBUS RTU 轮询参数,并定期上报设备状态信息。
|
||
|
||
**新增功能**:
|
||
- ✅ **离线数据存储**:网络断开时自动存储数据到Flash
|
||
- ✅ **自动补传**:网络恢复后自动补传离线数据到MQTT服务器
|
||
- ✅ **SPIFFS文件系统**:ESP-IDF原生支持,稳定可靠
|
||
- ✅ **8MB存储容量**:可存储约11小时离线数据(注意:SPIFFS最小文件4KB)
|
||
|
||
## SNTP 时间同步
|
||
|
||
设备内置 SNTP(Simple Network Time Protocol)时间同步功能,在网络连接成功后会自动同步系统时间。
|
||
|
||
### 时间同步特性
|
||
|
||
- **自动同步**:获取 IP 地址后自动启动 SNTP 客户端
|
||
- **多服务器支持**:配置了中国地区多个 NTP 服务器
|
||
- `cn.pool.ntp.org` - 中国 NTP 服务器
|
||
- `ntp1.aliyun.com` - 阿里云 NTP 服务器
|
||
- `ntp.tencent.com` - 腾讯云 NTP 服务器
|
||
- **时区设置**:自动设置为北京时间(CST-8)
|
||
- **超时保护**:等待时间同步最长 10 秒,超时后使用本地时间继续运行
|
||
|
||
### 时间同步状态
|
||
|
||
设备会在启动日志中显示时间同步状态:
|
||
|
||
```
|
||
I (xxxx) SNTP_ESP: 初始化SNTP服务
|
||
I (xxxx) SNTP_ESP: 时区设置为北京时间 (CST-8)
|
||
I (xxxx) SNTP_ESP: 当前时间: 1970-01-01 08:00:00 Thursday (同步前)
|
||
I (xxxx) SNTP_ESP: 时间同步成功: 2026-02-01 15:30:45 (同步后)
|
||
I (xxxx) SNTP_ESP: 时间同步完成
|
||
```
|
||
|
||
### 时间在系统中的应用
|
||
|
||
同步后的时间会在以下场景中使用:
|
||
|
||
1. **设备状态上报** - `update_time` 字段显示精确的同步时间
|
||
2. **数据采集记录** - 可扩展用于记录数据采集时间戳
|
||
3. **日志时间戳** - 方便调试和问题追踪
|
||
|
||
### 注意事项
|
||
|
||
- 需要网络连接正常才能进行时间同步
|
||
- 首次启动时,时间同步可能需要几秒钟
|
||
- 如果网络连接异常,设备会使用本地时间继续工作
|
||
- 时间同步完成后,系统时间会持续由 SNTP 守护进程维护
|
||
|
||
## 设备状态上报
|
||
|
||
### 上报主题
|
||
|
||
`CONFIG_MQTT_PUB_TOPIC` (在 `sdkconfig` 中配置)
|
||
|
||
### 上报类型
|
||
|
||
设备会定期上报以下类型的数据:
|
||
|
||
1. **设备状态**(`message_type: "device_status"`)
|
||
2. **MODBUS 采集数据**(`function_code: 3`)
|
||
|
||
### 设备状态上报格式
|
||
|
||
```json
|
||
{
|
||
"message_type": "device_status",
|
||
"mac_address": "D0:CF:13:1B:C3:94",
|
||
"ip_address": "192.168.1.100",
|
||
"chip_model": "ESP32-S3",
|
||
"idf_version": "v5.5.2-dirty",
|
||
"uptime": 16,
|
||
"uptime_desc": "16秒",
|
||
"free_heap": 312996,
|
||
"status": "online",
|
||
"status_desc": "在线",
|
||
"update_time": "2026-02-01 15:30:45",
|
||
"led1_state": 1,
|
||
"led1_desc": "常亮",
|
||
"led1_function": "网络状态灯",
|
||
"led2_state": 4,
|
||
"led2_desc": "心跳",
|
||
"led2_function": "通信状态灯",
|
||
"modbus_enabled": 1,
|
||
"modbus_enabled_desc": "启用",
|
||
"modbus_channel": 0,
|
||
"modbus_channel_desc": "通道0 (UART0)",
|
||
"modbus_slave_addr": 1,
|
||
"modbus_interval": 1000,
|
||
"heap_status": "充足"
|
||
}
|
||
```
|
||
|
||
### 字段说明
|
||
|
||
#### 基本信息
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `message_type` | string | 消息类型:`"device_status"` |
|
||
| `mac_address` | string | 设备 WiFi MAC 地址(唯一标识) |
|
||
| `ip_address` | string | 设备 IP 地址 |
|
||
| `chip_model` | string | 芯片型号 |
|
||
| `idf_version` | string | ESP-IDF 版本 |
|
||
|
||
#### 运行状态
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `uptime` | number | 设备运行时间(秒) |
|
||
| `uptime_desc` | string | 运行时间中文描述(如:"1天5小时30分") |
|
||
| `free_heap` | number | 剩余堆内存(字节) |
|
||
| `heap_status` | string | 内存状态:`"充足"` / `"一般"` / `"紧张"` |
|
||
| `status` | string | 设备状态:`"online"` |
|
||
| `status_desc` | string | 设备状态中文描述:`"在线"` |
|
||
| `update_time` | string | 状态更新时间(YYYY-MM-DD HH:MM:SS,通过 SNTP 同步) |
|
||
|
||
#### LED 状态
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `led1_state` | number | LED1 状态:0=关闭, 1=常亮, 2=慢闪, 3=快闪, 4=心跳 |
|
||
| `led1_desc` | string | LED1 状态中文描述 |
|
||
| `led1_function` | string | LED1 功能:`"网络状态灯"` |
|
||
| `led2_state` | number | LED2 状态:同 LED1 |
|
||
| `led2_desc` | string | LED2 状态中文描述 |
|
||
| `led2_function` | string | LED2 功能:`"通信状态灯"` |
|
||
|
||
#### MODBUS 轮询状态
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `modbus_enabled` | number | MODBUS 轮询是否启用(0=禁用, 1=启用) |
|
||
| `modbus_enabled_desc` | string | 轮询状态中文描述:`"启用"` / `"禁用"` / `"未配置"` |
|
||
| `modbus_channel` | number | 当前使用的 RS485 通道(0 或 1) |
|
||
| `modbus_channel_desc` | string | 通道中文描述 |
|
||
| `modbus_slave_addr` | number | 当前轮询的从机地址 |
|
||
| `modbus_interval` | number | 当前轮询间隔(毫秒) |
|
||
|
||
### 中文提示字段说明
|
||
|
||
带有 `_desc` 后缀的字段是中文提示字段,设计用于:
|
||
- **直接显示在 Web 页面上**,无需额外转换
|
||
- **不会被程序解析**,仅用于展示
|
||
- **提升用户体验**,让状态更直观
|
||
|
||
Web 页面可以选择显示:
|
||
- 程序解析字段(如 `led1_state`)用于逻辑判断
|
||
- 中文描述字段(如 `led1_desc`)用于界面显示
|
||
|
||
### 上报时机
|
||
|
||
- MQTT 订阅成功后立即上报一次
|
||
- 之后每隔 10 秒上报一次(可在 main.c 中修改 `mqtt_start_device_status_task(10000)` 的参数)
|
||
|
||
---
|
||
|
||
## MODBUS 控制说明
|
||
|
||
本设备是一个分布式采集网关,支持通过 MQTT 远程控制 MODBUS RTU 轮询参数。
|
||
|
||
## MQTT 控制指令
|
||
|
||
### 控制主题
|
||
|
||
发布控制指令到订阅主题:`CONFIG_MQTT_SUB_TOPIC` (在 `sdkconfig` 中配置)
|
||
|
||
### 指令格式 (JSON)
|
||
|
||
```json
|
||
{
|
||
"command": "modbus_poll",
|
||
"channel": 0,
|
||
"slave_addr": 1,
|
||
"start_addr": 0,
|
||
"reg_count": 2,
|
||
"interval": 1000,
|
||
"enabled": true
|
||
}
|
||
```
|
||
|
||
### 参数说明
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `command` | string | 是 | 固定为 `"modbus_poll"` |
|
||
| `channel` | number | 是 | RS485 通道号 (0 或 1) |
|
||
| `slave_addr` | number | 是 | 从机地址 (1-247) |
|
||
| `start_addr` | number | 是 | 起始寄存器地址 (0-65535) |
|
||
| `reg_count` | number | 是 | 读取寄存器数量 (1-125) |
|
||
| `interval` | number | 是 | 轮询间隔(毫秒),最小 100ms |
|
||
| `enabled` | boolean | 否 | 是否启用轮询,默认 `true` |
|
||
|
||
## 使用示例
|
||
|
||
### 示例 1:读取设备地址 1 的寄存器
|
||
|
||
```json
|
||
{
|
||
"command": "modbus_poll",
|
||
"channel": 0,
|
||
"slave_addr": 1,
|
||
"start_addr": 0,
|
||
"reg_count": 2,
|
||
"interval": 1000
|
||
}
|
||
```
|
||
|
||
设备会每 1 秒读取一次从机地址 1,从寄存器 0 开始的 2 个寄存器。
|
||
|
||
### 示例 2:读取设备地址 10 的寄存器
|
||
|
||
```json
|
||
{
|
||
"command": "modbus_poll",
|
||
"channel": 0,
|
||
"slave_addr": 10,
|
||
"start_addr": 0,
|
||
"reg_count": 5,
|
||
"interval": 2000
|
||
}
|
||
```
|
||
|
||
设备会每 2 秒读取一次从机地址 10,从寄存器 0 开始的 5 个寄存器。
|
||
|
||
### 示例 3:读取指定范围的寄存器
|
||
|
||
```json
|
||
{
|
||
"command": "modbus_poll",
|
||
"channel": 0,
|
||
"slave_addr": 1,
|
||
"start_addr": 10,
|
||
"reg_count": 4,
|
||
"interval": 500
|
||
}
|
||
```
|
||
|
||
设备会每 500ms 读取一次从机地址 1,从寄存器 10 开始的 4 个寄存器(地址 10, 11, 12, 13)。
|
||
|
||
### 示例 4:停止轮询
|
||
|
||
```json
|
||
{
|
||
"command": "modbus_poll",
|
||
"channel": 0,
|
||
"slave_addr": 1,
|
||
"start_addr": 0,
|
||
"reg_count": 2,
|
||
"interval": 1000,
|
||
"enabled": false
|
||
}
|
||
```
|
||
|
||
设置 `"enabled": false` 可以暂停轮询任务。
|
||
|
||
### 示例 5:使用 RS485 通道 1
|
||
|
||
```json
|
||
{
|
||
"command": "modbus_poll",
|
||
"channel": 1,
|
||
"slave_addr": 5,
|
||
"start_addr": 0,
|
||
"reg_count": 10,
|
||
"interval": 1000
|
||
}
|
||
```
|
||
|
||
使用 RS485 通道 1 进行轮询。
|
||
|
||
## 数据上报
|
||
|
||
### 上报主题
|
||
|
||
`CONFIG_MQTT_PUB_TOPIC` (在 `sdkconfig` 中配置)
|
||
|
||
### 上报数据格式 (JSON)
|
||
|
||
```json
|
||
{
|
||
"channel": "RS485-1",
|
||
"slave_addr": 1,
|
||
"function_code": 3,
|
||
"status": "success",
|
||
"byte_count": 4,
|
||
"register_count": 2,
|
||
"registers": [
|
||
556,
|
||
998
|
||
]
|
||
}
|
||
```
|
||
|
||
### 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `channel` | string | RS485 通道名称 |
|
||
| `slave_addr` | number | 从机地址 |
|
||
| `function_code` | number | MODBUS 功能码(始终为 3) |
|
||
| `status` | string | 状态:`"success"` 或 `"exception"` |
|
||
| `byte_count` | number | 数据字节数 |
|
||
| `register_count` | number | 寄存器数量 |
|
||
| `registers` | array | 寄存器值数组 |
|
||
|
||
### 异常响应格式
|
||
|
||
```json
|
||
{
|
||
"channel": "RS485-1",
|
||
"slave_addr": 1,
|
||
"function_code": 131,
|
||
"status": "exception",
|
||
"exception_code": 2
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `exception_code` | number | 异常码:<br>1 - 非法功能<br>2 - 非法数据地址<br>3 - 非法数据值<br>4 - 服务器设备故障 |
|
||
|
||
## 动态更新
|
||
|
||
每次发送 MQTT 控制指令都会立即更新轮询参数,无需重启设备。
|
||
|
||
### 更新流程
|
||
|
||
1. 发送新的控制指令
|
||
2. 设备接收并解析 JSON
|
||
3. 立即更新轮询配置
|
||
4. 下一次轮询使用新参数
|
||
|
||
## MQTT 控制指令
|
||
|
||
### 控制主题
|
||
|
||
发布控制指令到订阅主题:`CONFIG_MQTT_SUB_TOPIC` (在 `sdkconfig` 中配置)
|
||
|
||
### 指令格式 (JSON)
|
||
|
||
```json
|
||
{
|
||
"command": "modbus_poll",
|
||
"channel": 0,
|
||
"slave_addr": 1,
|
||
"start_addr": 0,
|
||
"reg_count": 2,
|
||
"interval": 1000,
|
||
"enabled": true
|
||
}
|
||
```
|
||
|
||
### 参数说明
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `command` | string | 是 | 固定为 `"modbus_poll"` |
|
||
| `channel` | number | 是 | RS485 通道号 (0 或 1) |
|
||
| `slave_addr` | number | 是 | 从机地址 (1-247) |
|
||
| `start_addr` | number | 是 | 起始寄存器地址 (0-65535) |
|
||
| `reg_count` | number | 是 | 读取寄存器数量 (1-125) |
|
||
| `interval` | number | 是 | 轮询间隔(毫秒),最小 100ms |
|
||
| `enabled` | boolean | 否 | 是否启用轮询,默认 `true` |
|
||
|
||
### 支持的指令类型
|
||
|
||
当前支持以下控制指令:
|
||
|
||
#### 1. MODBUS 轮询控制
|
||
|
||
```json
|
||
{
|
||
"command": "modbus_poll",
|
||
"channel": 0,
|
||
"slave_addr": 1,
|
||
"start_addr": 0,
|
||
"reg_count": 2,
|
||
"interval": 1000,
|
||
"enabled": true
|
||
}
|
||
```
|
||
|
||
#### 2. 设备状态上报间隔控制(可选扩展)
|
||
|
||
可通过修改 `main.c` 中的参数来调整状态上报间隔:
|
||
|
||
```c
|
||
mqtt_start_device_status_task(10000); // 10000ms = 10秒
|
||
```
|
||
|
||
或运行时调用 API:
|
||
|
||
```c
|
||
mqtt_update_report_interval(5000); // 5秒
|
||
```
|
||
|
||
## 离线数据存储
|
||
|
||
### 功能特性
|
||
|
||
设备支持离线数据存储功能,确保网络断开时数据不丢失:
|
||
|
||
- **自动检测**:实时监测网络状态(通过MQTT连接状态)
|
||
- **离线存储**:网络断开时自动存储数据到Flash
|
||
- **自动补传**:网络恢复后自动补传离线数据到MQTT服务器
|
||
- **LittleFS文件系统**:提供断电保护和磨损均衡
|
||
- **8MB存储容量**:可存储约11小时离线数据
|
||
|
||
### 存储容量
|
||
|
||
| 数据类型 | 数据量 | 可存储时长 |
|
||
|---------|--------|----------|
|
||
| MODBUS数据 | 200字节/条(1秒/次) | 约11小时(注意:SPIFFS最小文件4KB) |
|
||
|
||
### 工作流程
|
||
|
||
#### 正常模式(网络在线)
|
||
```
|
||
数据产生 → 直接发布到MQTT → 不存储
|
||
```
|
||
|
||
#### 离线模式(网络断开)
|
||
```
|
||
数据产生 → 存储到Flash SPIFFS → 等待网络恢复
|
||
```
|
||
|
||
#### 补传模式(网络恢复)
|
||
```
|
||
检测网络恢复 → 读取最旧数据 → 发布到MQTT → 删除已传数据 → 继续下一条
|
||
```
|
||
|
||
### 存储结构
|
||
|
||
```
|
||
/flash/
|
||
├── data/
|
||
│ ├── modbus/ # MODBUS采集数据
|
||
│ │ ├── 1738452345.json # 时间戳命名
|
||
│ │ └── ...
|
||
│ └── status/ # 设备状态数据
|
||
│ └── ...
|
||
└── index.json # 索引文件
|
||
```
|
||
|
||
### 日志示例
|
||
|
||
#### 存储离线数据
|
||
```
|
||
I (xxxx) mqtt_esp: Network is offline, stopping offline data upload
|
||
I (xxxx) mqtt_esp: Storing offline data (type=1, size=256)
|
||
I (xxxx) OFFLINE_STORAGE: Storage usage: 512 / 8388608 bytes (0.0%)
|
||
```
|
||
|
||
#### 补传离线数据
|
||
```
|
||
I (xxxx) mqtt_esp: Network is online, starting offline data upload
|
||
I (xxxx) mqtt_esp: Found 10 offline data files, uploading...
|
||
I (xxxx) mqtt_esp: Publishing offline data (type=1, size=256)
|
||
I (xxxx) mqtt_esp: Offline data published successfully, msg_id=12345
|
||
```
|
||
|
||
### 存储策略
|
||
|
||
- **自动循环覆盖**:最多存储10,000个文件,超出自动删除最旧数据
|
||
- **时间戳排序**:按时间戳顺序补传,确保数据时间顺序
|
||
- **原子写入**:使用临时文件+重命名机制
|
||
- **断电保护**:SPIFFS提供基本的断电保护
|
||
|
||
### 注意事项
|
||
|
||
1. **首次启动**:会自动初始化并挂载SPIFFS文件系统
|
||
2. **空间管理**:存储满后自动循环覆盖最旧数据
|
||
3. **补传速率**:每秒处理一条数据,避免阻塞新数据上传
|
||
4. **Flash寿命**:SPIFFS无磨损均衡,建议定期格式化
|
||
5. **最小文件限制**:SPIFFS最小文件4KB,小文件会浪费空间
|
||
6. **格式化**:如需清空所有数据,可调用 `flash_spiffs_format()`
|
||
|
||
详细文档请参考:[离线数据存储和补传功能说明](docs/OFFLINE_STORAGE.md)
|
||
|
||
## 注意事项
|
||
|
||
1. **轮询间隔最小为 100ms**,设置更小的值会被拒绝
|
||
2. **寄存器数量最大为 125**,MODBUS RTU 限制
|
||
3. **从机地址范围 1-247**,0 为广播地址
|
||
4. **通道号只能是 0 或 1**
|
||
5. 首次发送指令后会自动启动轮询任务
|
||
6. **离线存储容量约11小时**,超出会循环覆盖最旧数据
|
||
|
||
## 使用 mosquitto_cli 测试
|
||
|
||
### 发送控制指令
|
||
|
||
```bash
|
||
mosquitto_pub -h <broker_ip> -p 1883 -t <sub_topic> -m '{
|
||
"command": "modbus_poll",
|
||
"channel": 0,
|
||
"slave_addr": 1,
|
||
"start_addr": 0,
|
||
"reg_count": 2,
|
||
"interval": 1000
|
||
}'
|
||
```
|
||
|
||
### 监听数据上报
|
||
|
||
```bash
|
||
mosquitto_sub -h <broker_ip> -p 1883 -t <pub_topic>
|
||
```
|
||
|
||
## 硬件连接
|
||
|
||
### RS485 通道 0 (UART0)
|
||
|
||
| 引脚 | GPIO | 功能 |
|
||
|------|------|------|
|
||
| RO (RX) | GPIO 41 | RS485 接收器输出 |
|
||
| DE/RE | GPIO 42 | 数据使能/接收器使能 |
|
||
| DI (TX) | GPIO 44 | RS485 驱动器输入 |
|
||
|
||
### RS485 通道 1 (UART2)
|
||
|
||
| 引脚 | GPIO | 功能 |
|
||
|------|------|------|
|
||
| RO (RX) | GPIO 43 | RS485 接收器输出 |
|
||
| DE/RE | GPIO 2 | 数据使能/接收器使能 |
|
||
| DI (TX) | GPIO 1 | RS485 驱动器输入 |
|