Files

532 lines
14 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.
# Distributed Collector Gateway - 使用说明
## 功能概述
本设备是一个分布式采集网关,支持通过 MQTT 远程控制 MODBUS RTU 轮询参数,并定期上报设备状态信息。
**新增功能**
-**离线数据存储**网络断开时自动存储数据到Flash
-**自动补传**网络恢复后自动补传离线数据到MQTT服务器
-**SPIFFS文件系统**ESP-IDF原生支持稳定可靠
-**8MB存储容量**可存储约11小时离线数据注意SPIFFS最小文件4KB
## SNTP 时间同步
设备内置 SNTPSimple 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 驱动器输入 |