CHANGELOG.md 18 KB

CHANGELOG.md - 变更日志

本文档持续更新,记录 MPPT Monitor 的所有变更、Bug 修复和功能新增。


[2.3.3] - 2026-09-03

关键 Bug 修复 - HTTP Web Modbus 设备参数设置写入失败

问题现象: 通过 Web 界面设置设备参数(如 Battery Type 电池类型)时,前端提示 ✗ 写入失败: ...,且 Modbus 实际写入极不稳定。

根因分析(深度排查):

  1. 【致命】FC 0x10 写多寄存器响应帧长度与 CRC 解析错误

    • Modbus 功能码 0x10(写多个寄存器)的正常响应帧为 8 字节: 地址(1) + 功能码(1) + 起始地址(2) + 寄存器数量(2) + CRC(2)
    • 但 modbus_write_register() 中:
      • 缓冲区声明为 uint8_t resp[6],只读 6 字节;
      • CRC 校验从 resp[4] | resp[5]<<8 读取,而 resp[4]/resp[5] 实际是"寄存器数量(0x00, 0x01)",并非 CRC。
    • 结果:每次写入都会因 CRC 校验必然失败而返回失败,这是 Web 写入一直报错的根本原因。
    • 修复:缓冲区扩大为 8 字节,正确从 resp[6]/resp[7] 读取 CRC,并校验起始地址回显与寄存器数量回显(须为 1)。
  2. 【并发】写入线程与轮询线程并行访问同一 TCP Socket

    • Modbus 轮询线程(poll_thread)与 Web 设置写入(modbus_write_register)在不同线程中同时对同一个 TCP Socket 收发,没有任何互斥保护。
    • 结果:写入期间轮询命令/响应相互交错,导致响应数据错乱、地址不匹配、超时等偶发故障。
    • 修复:引入专用互斥锁 sock_mutex,串行化所有 Socket 的 send/recv 操作(modbus_send_recv 与 modbus_write_register 均在持有锁的窗口内完成收发),彻底消除帧交错。
  3. 写入失败 500 响应缺少 CORS 头

    • handle_api_settings() 中写入失败的 500 响应缺少 Access-Control-Allow-Origin: *,浏览器因 CORS 拦截无法读取后端错误信息,前端只能显示笼统的请求失败提示。
    • 修复:为 500 错误响应补齐 CORS 头,保证前端可正常展示具体错误原因。
  4. 异常响应帧(5 字节)等待超时

    • FC 0x10 的异常响应帧为 5 字节(地址+0x90+异常码+CRC2),原逻辑强制等满 6 字节,异常时白白超时。
    • 修复:检测到 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
# 零警告零错误

[2.3.2] - 2026-08-28

Bug 修复 - 编译警告和 POSIX 兼容性

修复内容:

  1. 添加 POSIX 功能测试宏

    • 问题:getaddrinfo、usleep、freeaddrinfo 等 POSIX 函数在严格 C11 模式下未声明
    • 修复:在所有源文件顶部添加 #define _GNU_SOURCE
    • 影响文件:src/main.c、src/modbus_client.c、src/web_server.c、src/db.c、src/config.c
  2. 修复未使用参数警告

    • 问题:多个 handler 函数的 hm 参数未使用,触发 -Wunused-parameter 警告
    • 修复:Makefile 添加 -Wno-unused-parameter 编译选项
  3. 代码质量检查

    • 检查项:缓冲区溢出、内存泄漏、NULL 指针、文件句柄泄漏
    • 结果:所有文件句柄正确关闭,无内存泄漏,无缓冲区溢出风险

验证结果:

$ make clean && make
cc -Wall -Wextra -Wno-unused-parameter -O2 -g ...
Build complete: build/mppt_monitor
# 零警告零错误

[2.3.1] - 2026-08-28

Bug 修复 - HTTP Web Modbus 设备参数设置

修复内容:

  1. 添加设备地址范围验证

    • 问题:未验证 device_addr 范围,可能发送无效地址
    • 修复:添加 1-247 范围检查,返回明确错误信息
  2. 优化 Modbus 连接检查顺序

    • 问题:验证逻辑在连接检查之前执行,导致混淆的错误信息
    • 修复:将 Modbus 连接检查移到参数验证之前
    • 效果:设备离线时直接返回 "Modbus not connected",不再执行无效验证
  3. 统一错误响应格式

    • 所有错误响应添加 CORS 头 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 头 | ✅ |


[2.3.0] - 2026-08-27

升级 mongoose 7.22 → 7.23

  • 替换 lib/mongoose-7.22/ 为 lib/mongoose-7.23/
  • 更新 Makefile 中 MONGOOSE_DIR 路径
  • 更新 src/web_server.c 中 #include 路径
  • 所有 API 函数调用兼容 mongoose 7.23 版本
  • 编译通过,零警告零错误

[2.2.0] - 2026-08-06

UI 优化:页面布局居中 + 历史查询默认时间范围

  • 导航菜单居中:顶部导航栏居中显示
  • 内容区域居中:主内容区域最大宽度 1400px,居中显示
  • 控件居中靠左:历史查询控件居中容器内靠左对齐
  • 历史查询默认时间:开始时间 = 中国时区(UTC+8)昨天 00:00,结束时间 = 中国时区当前时间
  • 快捷按钮使用本地时间:1h/24h/7d 快捷按钮使用本地时区格式化

[2.1.2] - 2026-08-06

Bug 修复:设置写入 Empty request body 和 Modbus 响应混淆

问题:

  1. POST /api/settings 返回 "Empty request body" 错误
  2. Modbus 写入响应与轮询响应混淆

根因:

  1. mongoose 7.22 的 hm->body 字段在某些情况下为空
  2. Modbus 写入和读取在同一 TCP 连接上,响应可能混淆

修复:

  1. 增强 body 解析:当 hm->body.buf 为空时,从 hm->message 原始数据中查找 \r\n\r\n 分隔符提取 body
  2. 添加详细的 HTTP 请求调试日志
  3. 修复 use-after-free:在 cJSON_Delete 前复制 param 到本地缓冲区

Bug 修复:历史记录存储

问题: 历史记录查询不到数据

根因: 存储线程等待时间过长,设备未上线时错过首次存储

修复:

  1. 存储线程在设备上线后立即触发首次存储
  2. 添加存储成功/失败日志
  3. 优化存储时机,确保设备数据已更新

[2.1.1] - 2026-08-06

Bug 修复:设置写入 JSON 解析失败

问题: POST /api/settings 返回 "Empty request body" 错误

根因: mongoose 7.22 的 hm->body 字段在某些情况下为空,需要手动从原始消息中提取 body

修复:

  1. 添加 fallback 机制:当 hm->body.buf 为空时,从 hm->message 原始数据中查找 \r\n\r\n 分隔符提取 body
  2. 添加详细的 body 解析日志

Bug 修复:设置写入 param 字段乱码 (Use-After-Free)

问题

POST /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() 函数

[2.1.0] - 2026-08-06

新增:设备设置功能 (Device Settings)

功能描述

通过 Web 界面远程配置 MPPT 设备参数,使用 Modbus 功能码 0x10 (Write Multiple Registers)。

新增 API

  • 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" 标签页,包含参数选择表单和寄存器参考表

安全特性

  • 写入前参数范围校验
  • 自定义电池类型才能修改电压参数
  • 写入前确认对话框
  • 芯片擦写次数警告(最大100000次)

[2.0.1] - 2026-08-06

Bug 修复:Modbus 数据读取失败

问题描述

升级到 V2.0.0 后,设备无法读取数据,日志显示 Address mismatch: expected 1, got 105。

根因分析

  1. TCP 缓冲区残留数据:发送 Output Registers 请求前,Input Registers 的响应数据可能未完全读取,残留字节被误解析为新响应的地址字节
  2. 指令间隔不足:协议规范要求两条指令间隔 1 秒,原代码仅等待 200ms
  3. 不完整帧读取:recv() 单次调用可能只读到部分响应,导致后续解析错位

修复内容

  • 新增 drain_socket() 函数:每次发送前清空 TCP 缓冲区残留数据
  • 实现完整 Modbus RTU 帧读取:根据 byte_count 字段判断帧完整性,循环读取直到完整
  • 连接建立后增加 200ms 稳定等待 + 缓冲区清空
  • 两条指令间隔从 200ms 增加到 1000ms(符合协议规范)

验证结果

Device #1 [ONLINE] Batt:28.0V(100%) PV:42.0V/1.7A/72W Temp:48C Fault:Normal
Type:Custom (自定义) Mode:DC-DC SW:v115

[2.0.0] - 2026-08-05

重大更新:DM Series ModBus Protocol V2.1 完整支持

新增功能

  • 完整寄存器读取:支持全部 Input Registers (0x04, 30001-30011) 和 Output Registers (0x03, 40001-40009)
  • 多设备支持:可同时轮询最多 16 个设备(地址 1-247),通过 config.ini 配置
  • 完整设备信息展示:
    • 电池类型(胶体/密封/磷酸铁锂/三元锂电/自定义/加水)
    • 电池等级(自动识别/12V/24V/36V/48V/60V/72V)
    • 充电模式(MPPT/DC-DC)
    • 充电电流限制、过放电压、提升电压、充满电压、恢复电压
    • 485 地址设置、软件版本号
    • 完整故障代码描述(20=正常, 30=光伏板超压, 31=过流, 32=电池低压, 33=电池超压, 38=温度高, 50=传感器故障, 88=未解密)
  • 多设备仪表盘:每个设备独立卡片展示,实时刷新
  • 设备列表 API: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 - 写配置(预留)

[1.1.0] - 2026-08-04

新增:历史数据存储与查询

新增功能

  • SQLite3 历史数据存储

    • 新增 db.h/db.c 数据库模块
    • 自动创建 history_data 表,包含所有 MPPT 寄存器字段
    • 可配置的定时存储(默认每 10 秒保存一次实时数据)
    • 支持时间范围查询、分页、排序
  • config.ini 配置文件

    • 新增 config.h/config.c 配置解析模块
    • 支持 [modbus]、[storage]、[web] 三个配置段
    • 可配置项:Modbus 连接参数、存储间隔、数据库路径、Web 端口、分页大小
    • 配置文件缺失时使用内置默认值,不影响启动
  • 历史数据查询 API

    • GET /api/history - 分页查询历史数据
    • 参数:start_time、end_time(ISO 8601 格式)、page、page_size、sort(ASC/DESC)
    • 返回:total、page、page_size、total_pages、records[]
    • 每条记录包含完整的 MPPT 数据和计算后的光伏功率
  • 历史数据仪表盘页面

    • 实时监控 / 历史查询双页面切换(顶部 Tab 导航)
    • 日期时间范围选择器(datetime-local 输入)
    • 快捷时间按钮:最近 1 小时 / 24 小时 / 7 天
    • 分页大小选择:20 / 50 / 100 / 200 条
    • 排序切换:最新优先 / 最早优先
    • 数据表格展示:时间、电池电压、SOC、光伏电压/电流/功率、温度、故障、状态
    • 分页控件:上一页 / 下一页 / 页码显示
    • 分页信息:显示当前范围和总记录数
  • 趋势图表(Chart.js 3.x)

    • 通过 CDN 引入 Chart.js,无本地依赖
    • 多数据集折线图:电池电压、电池 SOC、光伏功率、温度
    • 数据集可切换显示/隐藏
    • 自适应时间轴标签
    • 响应式布局,跟随页面宽度

架构变更

  • 主程序新增数据库初始化和关闭流程
  • Modbus 轮询线程新增定时存储逻辑(基于 storage_interval_sec 配置)
  • Web 上下文 web_ctx_t 新增 db 和 config 指针
  • Makefile 新增 -lsqlite3 链接和 config.c、db.c 编译目标

依赖变更

库 版本 用途
SQLite3 3.x 历史数据存储(新增)

[1.0.0] - 2026-08-04

初始版本发布

新增功能

  • Modbus RTU over TCP 客户端

    • 连接 discover.zhonjin.com:40635 读取 MPPT 充电控制器数据
    • 支持 10 个输入寄存器(30001~30010)批量读取
    • CRC-16/Modbus 校验确保数据完整性
    • 10 秒连接超时,自动重连机制
    • 连续 3 次读取失败后自动断开重连
  • Mongoose 7.22 HTTP Web 服务器

    • 监听端口 8085
    • GET /api/data - 返回完整 MPPT 实时数据(JSON)
    • GET /api/status - 返回连接与设备状态(JSON)
    • GET /api/health - 健康检查端点
    • GET / - HTML 实时监控仪表盘(每 2 秒自动刷新)
    • Web 服务器看门狗:自动检测并重启失败的 HTTP 服务
  • mongoose-7.22 新 API 适配

    • 使用 mg_match() 替代已废弃的 mg_http_match_uri()
    • 使用 struct mg_str { char *buf; size_t len; } 新结构体
    • 全部 API 调用遵循 mongoose-7.22 规范
  • cJSON 1.7.19 JSON 序列化

    • 使用 cJSON 构建结构化 JSON 响应
    • 支持嵌套对象(battery, pv, device)
    • 包含原始寄存器数据用于调试
  • 多线程架构

    • Modbus 数据采集线程(独立线程,2 秒轮询)
    • HTTP 服务主线程(Mongoose 事件循环)
    • pthread_mutex 保护共享数据一致性
  • 跨平台兼容

    • Ubuntu 24.04 LTS (x86_64) 已验证
    • Raspberry Pi 5 8G (aarch64) 兼容设计
    • 自动检测 OpenSSL 路径(pkg-config / 手动路径 / 自定义路径)
  • 构建系统

    • Makefile 支持 make / make clean / make run / make debug
    • 支持 make install 安装到系统
    • 支持 make systemd 配置开机自启服务
    • 零警告编译 (-Wall -Wextra)
  • 文档

    • INSTALL.md - 编译安装配置文档(含树莓派详细配置)
    • TECHNICAL.md - 技术方案文档(协议细节、API 设计)
    • CHANGELOG.md - 本文档

寄存器数据解析

寄存器 参数 换算
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