# 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 | 异常码:
1 - 非法功能
2 - 非法数据地址
3 - 非法数据值
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 -p 1883 -t -m '{ "command": "modbus_poll", "channel": 0, "slave_addr": 1, "start_addr": 0, "reg_count": 2, "interval": 1000 }' ``` ### 监听数据上报 ```bash mosquitto_sub -h -p 1883 -t ``` ## 硬件连接 ### 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 驱动器输入 |