本文档持续更新,记录 MPPT Monitor 的所有变更、Bug 修复和功能新增。
问题现象: 通过 Web 界面设置设备参数(如 Battery Type 电池类型)时,前端提示 ✗ 写入失败: ...,且 Modbus 实际写入极不稳定。
根因分析(深度排查):
【致命】FC 0x10 写多寄存器响应帧长度与 CRC 解析错误
0x10(写多个寄存器)的正常响应帧为 8 字节:
地址(1) + 功能码(1) + 起始地址(2) + 寄存器数量(2) + CRC(2)modbus_write_register() 中:
uint8_t resp[6],只读 6 字节;resp[4] | resp[5]<<8 读取,而 resp[4]/resp[5] 实际是"寄存器数量(0x00, 0x01)",并非 CRC。resp[6]/resp[7] 读取 CRC,并校验起始地址回显与寄存器数量回显(须为 1)。【并发】写入线程与轮询线程并行访问同一 TCP Socket
poll_thread)与 Web 设置写入(modbus_write_register)在不同线程中同时对同一个 TCP Socket 收发,没有任何互斥保护。sock_mutex,串行化所有 Socket 的 send/recv 操作(modbus_send_recv 与 modbus_write_register 均在持有锁的窗口内完成收发),彻底消除帧交错。写入失败 500 响应缺少 CORS 头
handle_api_settings() 中写入失败的 500 响应缺少 Access-Control-Allow-Origin: *,浏览器因 CORS 拦截无法读取后端错误信息,前端只能显示笼统的请求失败提示。异常响应帧(5 字节)等待超时
resp[1] & 0x80(异常标识)后立即以 5 字节结束读取,快速反馈设备拒绝码。影响文件:
src/modbus_client.c:重写 modbus_write_register() 响应解析;modbus_send_recv() 增加 sock_mutex 保护;modbus_init()/modbus_stop() 初始化和销毁互斥锁;read_input_registers()/read_output_registers()/poll_device() 适配传递互斥锁。src/modbus_client.h:结构体新增 pthread_mutex_t sock_mutex 字段。src/web_server.c:写入失败 500 响应补充 CORS 头。验证结果:
$ make clean && make
Build complete: build/mppt_monitor
# 零警告零错误
修复内容:
添加 POSIX 功能测试宏
getaddrinfo、usleep、freeaddrinfo 等 POSIX 函数在严格 C11 模式下未声明#define _GNU_SOURCEsrc/main.c、src/modbus_client.c、src/web_server.c、src/db.c、src/config.c修复未使用参数警告
hm 参数未使用,触发 -Wunused-parameter 警告-Wno-unused-parameter 编译选项代码质量检查
验证结果:
$ make clean && make
cc -Wall -Wextra -Wno-unused-parameter -O2 -g ...
Build complete: build/mppt_monitor
# 零警告零错误
修复内容:
添加设备地址范围验证
优化 Modbus 连接检查顺序
统一错误响应格式
Access-Control-Allow-Origin: *验证结果: | 测试场景 | 预期结果 | 实际结果 | |---------|---------|---------| | 有效参数 + 设备离线 | Modbus not connected | ✅ | | device_addr=0 | Invalid device_addr | ✅ | | device_addr=248 | Invalid device_addr | ✅ | | 无效参数名 | Modbus not connected (先检查连接) | ✅ | | 缺少字段 | Missing fields | ✅ | | 空请求体 | Empty request body | ✅ | | GET 请求 | 返回可写参数列表 | ✅ | | OPTIONS 预检 | 204 + CORS 头 | ✅ |
lib/mongoose-7.22/ 为 lib/mongoose-7.23/Makefile 中 MONGOOSE_DIR 路径src/web_server.c 中 #include 路径问题:
/api/settings 返回 "Empty request body" 错误根因:
hm->body 字段在某些情况下为空修复:
hm->body.buf 为空时,从 hm->message 原始数据中查找 \r\n\r\n 分隔符提取 bodycJSON_Delete 前复制 param 到本地缓冲区问题: 历史记录查询不到数据
根因: 存储线程等待时间过长,设备未上线时错过首次存储
修复:
问题: POST /api/settings 返回 "Empty request body" 错误
根因: mongoose 7.22 的 hm->body 字段在某些情况下为空,需要手动从原始消息中提取 body
修复:
hm->body.buf 为空时,从 hm->message 原始数据中查找 \r\n\r\n 分隔符提取 bodyPOST /api/settings 写入设备参数时,响应 JSON 中 param 字段显示乱码(如 "param":"b,"),浏览器端报 "Invalid JSON body" 错误。
param 指针指向 cJSON_Parse() 解析后的 req 对象内部内存(j_param->valuestring)。在调用 cJSON_Delete(req) 释放内存后,param 成为悬空指针,后续 snprintf 使用它导致读取已释放内存,产生乱码。
在 cJSON_Delete(req) 之前,将 param 字符串复制到本地缓冲区 param_copy[64],后续所有响应构造均使用 param_copy。
// 修复前(错误)
cJSON_Delete(req); // 释放内存
snprintf(resp, ..., param); // 使用悬空指针 → 乱码
// 修复后(正确)
char param_copy[64];
snprintf(param_copy, sizeof(param_copy), "%s", param); // 提前复制
cJSON_Delete(req);
snprintf(resp, ..., param_copy); // 使用安全副本
src/web_server.c - handle_api_settings() 函数通过 Web 界面远程配置 MPPT 设备参数,使用 Modbus 功能码 0x10 (Write Multiple Registers)。
GET /api/settings - 返回可写寄存器列表和说明POST /api/settings - 写入设备参数
{"device_addr": 1, "param": "battery_type", "value": 2}{"success": true, "param_desc": "电池类型", "value": 2, "message": "写入成功"}| 参数名 | 寄存器 | 范围 | 说明 |
|---|---|---|---|
| battery_type | 40001 (0x0000) | 0-5 | 电池类型 |
| battery_level | 40002 (0x0001) | 0-6 | 电池电压等级 |
| charge_current_limit | 40003 (0x0002) | 5-100 | 充电电流限制(%) |
| full_charge_voltage | 40006 (0x0005) | 80-600 | 充满电压(0.1V),仅自定义电池可写 |
| device_address | 40008 (0x0007) | 0-247 | 485设备地址 |
| charge_mode | 40009 (0x0008) | 0-1 | 0=MPPT, 1=DCDC |
src/modbus_client.h/c: 新增 modbus_write_register() 和 modbus_validate_setting()src/web_server.c: 新增 handle_api_settings() 处理函数www/index.html: 新增 "Device Settings" 标签页,包含参数选择表单和寄存器参考表升级到 V2.0.0 后,设备无法读取数据,日志显示 Address mismatch: expected 1, got 105。
recv() 单次调用可能只读到部分响应,导致后续解析错位drain_socket() 函数:每次发送前清空 TCP 缓冲区残留数据Device #1 [ONLINE] Batt:28.0V(100%) PV:42.0V/1.7A/72W Temp:48C Fault:Normal
Type:Custom (自定义) Mode:DC-DC SW:v115
config.ini 配置GET /api/devices 返回所有设备摘要信息device_addr 参数)history_data 表包含全部 20+ 字段config.h/config.c - INI 配置文件解析模块db.h/db.c - SQLite3 数据库模块modbus_client.h/c - 完整 DM Series V2.1 协议支持web_server.h/c - 多设备 API + 外部 HTML 仪表盘www/index.html - 独立前端仪表盘(Chart.js 图表)Makefile - 链接 SQLite3| 功能码 | 方向 | 说明 |
|---|---|---|
| 0x04 | Read | Input Registers (30001-30011) - 实时数据 |
| 0x03 | Read | Output Registers (40001-40009) - 配置参数 |
| 0x10 | Write | Write Multiple Registers - 写配置(预留) |
SQLite3 历史数据存储
db.h/db.c 数据库模块history_data 表,包含所有 MPPT 寄存器字段config.ini 配置文件
config.h/config.c 配置解析模块[modbus]、[storage]、[web] 三个配置段历史数据查询 API
GET /api/history - 分页查询历史数据start_time、end_time(ISO 8601 格式)、page、page_size、sort(ASC/DESC)total、page、page_size、total_pages、records[]历史数据仪表盘页面
趋势图表(Chart.js 3.x)
storage_interval_sec 配置)web_ctx_t 新增 db 和 config 指针-lsqlite3 链接和 config.c、db.c 编译目标| 库 | 版本 | 用途 |
|---|---|---|
| SQLite3 | 3.x | 历史数据存储(新增) |
Modbus RTU over TCP 客户端
Mongoose 7.22 HTTP Web 服务器
GET /api/data - 返回完整 MPPT 实时数据(JSON)GET /api/status - 返回连接与设备状态(JSON)GET /api/health - 健康检查端点GET / - HTML 实时监控仪表盘(每 2 秒自动刷新)mongoose-7.22 新 API 适配
mg_match() 替代已废弃的 mg_http_match_uri()struct mg_str { char *buf; size_t len; } 新结构体cJSON 1.7.19 JSON 序列化
多线程架构
跨平台兼容
构建系统
文档
| 寄存器 | 参数 | 换算 |
|---|---|---|
| 30001 | 电池电压 | raw / 10 = V |
| 30002 | 光伏电压 | raw / 10 = V |
| 30003 | 光伏充电电流 | raw / 10 = A |
| 30004 | 累计发电量 | raw / 10 = kWh |
| 30005 | 机器温度 | raw = °C |
| 30006 | 故障代码 | raw |
| 30007 | 电池电量 | raw = % |
| 30008 | 电池串数 | raw |
| 30009 | 充电状态 | raw |
| 30010 | 设备类型 | raw |
| 库 | 版本 | 用途 |
|---|---|---|
| Mongoose | 7.23 | HTTP 服务器 |
| cJSON | 1.7.19 | JSON 序列化 |
| SQLite3 | 3.x | 历史数据存储 |
| OpenSSL | 3.x | TLS 支持(可选) |
| pthread | - | 多线程 |
# Ubuntu 24.04 / 树莓派 5
make
# 自定义 OpenSSL 路径
gcc src/main.c src/modbus_client.c src/web_server.c \
lib/cJSON-1.7.19/cJSON.c lib/mongoose-7.22/mongoose.c \
-W -Wall -Wextra -g -I. -Isrc \
-Ilib/cJSON-1.7.19 -Ilib/mongoose-7.22 \
-DMG_ENABLE_OPENSSL=1 \
-I/opt/openAI/Lib64U/include \
-L/opt/openAI/Lib64U/lib -lssl -lcrypto \
-lpthread -lm -o mppt_monitor
后续变更请按以下格式记录:
## [版本号] - 日期
### 新增功能
- 描述新增的功能
### Bug 修复
- 描述修复的问题
### 变更/优化
- 描述改进的内容
### 已知问题
- 描述已知但未修复的问题
最后更新: 2026-08-04