TECHNICAL.md 14 KB

TECHNICAL.md - 技术方案文档

本文档持续更新,记录 MPPT Monitor 的完整技术架构、协议细节、API 设计。


一、系统概述

1.1 项目目标

开发一个轻量级 C 语言嵌入式监控程序,通过 Modbus RTU over TCP 协议读取 MPPT 太阳能充电控制器的实时数据,并通过 HTTP REST API 对外提供 JSON 格式的实时数据接口。支持历史数据定时存储到 SQLite3 数据库,并提供带趋势图表的历史数据查询页面。

1.2 架构总览

┌─────────────────────────────────────────────────────────┐
│                    MPPT Monitor                          │
│                                                          │
│  ┌──────────────┐     ┌──────────────┐                  │
│  │ Modbus TCP   │     │ Mongoose HTTP│                  │
│  │ Client       │     │ Server :8085 │                  │
│  │ (Thread)     │     │ (Main Thread)│                  │
│  └──────┬───────┘     └──────┬───────┘                  │
│         │                     │                          │
│         ▼                     ▼                          │
│  ┌──────────────────────────────────┐                   │
│  │     Shared Data (mppt_data_t)    │                   │
│  │     Protected by pthread_mutex   │                   │
│  └──────────┬───────────────────────┘                   │
│             │                                           │
│             ▼                                           │
│  ┌──────────────────────┐                              │
│  │  SQLite3 Database    │                              │
│  │  (历史数据存储)       │                              │
│  └──────────────────────┘                              │
│                                                          │
└─────────────────────────────────────────────────────────┘
         │                           │
         ▼                           ▼
  ┌─────────────┐           ┌─────────────────┐
  │ MPPT Device │           │ Web Browser /   │
  │ TCP:40635   │           │ REST Client     │
  └─────────────┘           └─────────────────┘

1.3 线程模型

线程 职责 说明
Main Thread Mongoose HTTP 事件循环 处理 HTTP 请求、Web 服务器看门狗
Modbus Thread Modbus 数据采集 每 2 秒读取一次寄存器,自动重连

二、Modbus 通信协议

2.1 连接信息

参数 值
协议 Modbus RTU over TCP(原始 TCP Socket)
目标地址 discover.zhonjin.com
目标端口 40635
从机地址 0x01
功能码 0x04(读输入寄存器)
起始寄存器 0x0000(对应 30001)
寄存器数量 10(30001~30010)
超时时间 10 秒
轮询间隔 2000ms

2.2 请求帧格式

发→ 01 04 00 00 00 0A 70 0D

字节解析:
  01       - 从机地址 (Slave Address)
  04       - 功能码: 读输入寄存器 (Read Input Registers)
  00 00    - 起始寄存器地址 (0x0000 = 30001)
  00 0A    - 寄存器数量 (10个)
  70 0D    - CRC-16 校验 (低字节在前)

2.3 响应帧格式

收← 01 04 14 01 0C 01 81 00 10 00 07 00 2E 00 14 00 60 00 02 00 01 00 3C E9 F1

字节解析:
  01       - 从机地址
  04       - 功能码
  14       - 数据长度 (20字节 = 10个寄存器 × 2字节)
  01 0C    - 寄存器1: 电池电压 (268 → 26.8V)
  01 81    - 寄存器2: 光伏电压 (385 → 38.5V)
  00 10    - 寄存器3: 光伏充电电流 (16 → 1.6A)
  00 07    - 寄存器4: 累计发电量 (7 → 0.7kWh)
  00 2E    - 寄存器5: 机器温度 (46 → 46°C)
  00 14    - 寄存器6: 故障代码 (20 → 正常)
  00 60    - 寄存器7: 电池电量 (96 → 96%)
  00 02    - 寄存器8: 电池串数 (2)
  00 01    - 寄存器9: 充电状态 (1 → 充电中)
  00 3C    - 寄存器10: 设备类型 (60)
  E9 F1    - CRC-16 校验

2.4 寄存器映射表

寄存器 地址 参数名 原始值 实际值 单位 说明
30001 0x0000 电池电压 raw/10 V 24V系统正常范围 20-30V
30002 0x0001 光伏电压 raw/10 V 光伏板输出电压
30003 0x0002 光伏充电电流 raw/10 A 当前充电电流
30004 0x0003 累计发电量 raw/10 kWh 历史总发电量
30005 0x0004 机器温度 raw °C 控制器温度
30006 0x0005 故障代码 raw - 0/20=正常
30007 0x0006 电池电量 raw % SOC 百分比
30008 0x0007 电池串数 raw - 保留字段
30009 0x0008 充电状态 raw - 0=空闲 1=充电 2=吸收 3=浮充
30010 0x0009 设备类型 raw - 设备型号代码

2.5 CRC-16/Modbus 算法

多项式: 0xA001 (0x8005 的反转)
初始值: 0xFFFF
字节序: 低字节在前 (Little-Endian on wire)

算法流程:
  1. 初始化 CRC = 0xFFFF
  2. 对每个字节:
     a. CRC ^= byte
     b. 循环 8 次:
        - 如果 CRC 最低位为 1: CRC = (CRC >> 1) ^ 0xA001
        - 否则: CRC >>= 1
  3. 返回 CRC 值

三、HTTP REST API

3.1 服务配置

参数 值
监听地址 0.0.0.0
监听端口 8085
HTTP 库 Mongoose 7.22
JSON 库 cJSON 1.7.19

3.2 API 端点

GET /api/data - 获取完整 MPPT 数据

响应示例:

{
  "timestamp": "2026-08-04T15:30:00",
  "data_valid": true,
  "battery": {
    "voltage": 26.8,
    "soc": 96,
    "string_count": 2
  },
  "pv": {
    "voltage": 38.5,
    "current": 1.6,
    "power": 61.6
  },
  "cumulative_energy_kwh": 0.7,
  "device": {
    "temperature": 46.0,
    "fault_code": 20,
    "fault_desc": "Normal",
    "charge_state": 1,
    "charge_state_desc": "Charging (Bulk)",
    "device_type": 60
  },
  "raw_registers": [268, 385, 16, 7, 46, 20, 96, 2, 1, 60]
}

GET /api/status - 获取连接与设备状态

响应示例:

{
  "connection": {
    "state": "connected",
    "host": "discover.zhonjin.com",
    "port": 40635,
    "reconnect_count": 0
  },
  "device_online": true,
  "consecutive_failures": 0,
  "last_update": "2026-08-04T15:30:00"
}

GET /api/health - 健康检查

响应:

{"status": "ok"}

GET / - HTML 监控面板

返回内嵌的 HTML 仪表盘页面,每 2 秒自动刷新数据。


四、Mongoose 7.22 API 迁移指南

4.1 新旧 API 对比

旧 API (7.x 早期) 新 API (7.22) 说明
mg_http_match_uri(hm, "/path") mg_match(hm->uri, mg_str("/path"), NULL) URI 匹配
struct mg_str { const char *ptr; size_t len; } struct mg_str { char *buf; size_t len; } 字符串结构体
hm->uri.ptr hm->uri.buf 访问字符串数据

4.2 关键变更

  1. URI 匹配: 使用 mg_match() 替代 mg_http_match_uri()

    // 旧方式(已废弃)
    if (mg_http_match_uri(hm, "/api/data")) { ... }
    
    // 新方式(mongoose-7.22)
    if (mg_match(hm->uri, mg_str("/api/data"), NULL)) { ... }
    
  2. 字符串结构体: ptr 改为 buf

    // 旧方式
    const char *data = hm->uri.ptr;
    
    // 新方式
    char *data = hm->uri.buf;
    
  3. 通配符匹配: 支持 ?(单字符)、*(不含/)、#(含/)

    // 匹配 /api/ 下所有路径
    mg_match(hm->uri, mg_str("/api/#"), NULL)
    

五、错误处理与容错机制

5.1 Modbus 连接容错

连接失败 → 等待 2 秒 → 重试
读取超时 → 记录失败计数
连续 3 次失败 → 断开连接 → 重新建立连接
DNS 解析失败 → 等待后重试

5.2 Web 服务器容错

主循环每 5 秒检查一次 Web 服务器状态
如果监听连接丢失 → 自动重新启动 HTTP 监听
SIGPIPE 信号被忽略 → 防止客户端断开导致进程退出

5.3 数据一致性

Modbus 线程写入数据时使用 pthread_mutex 保护
Web 线程读取数据时加锁读取
确保 HTTP 响应中的数据不会在序列化过程中被修改

六、性能参数

指标 值 说明
数据采集间隔 2 秒 可通过 MODBUS_POLL_MS 调整
HTTP 响应延迟 < 1ms 纯内存数据,无磁盘 IO
内存占用 ~2MB 含 Mongoose 缓冲区
CPU 占用 < 1% 空闲时几乎不消耗 CPU
网络开销 ~30 bytes/2s Modbus 请求/响应

七、配置系统

7.1 config.ini 文件格式

[modbus]
host = discover.zhonjin.com
port = 40635
slave_addr = 0x01
timeout_sec = 10
poll_ms = 2000

[storage]
interval_sec = 10
db_path = mppt_data.db

[web]
port = 8085
page_size = 50

7.2 配置项说明

配置段 键 类型 默认值 说明
modbus host string discover.zhonjin.com Modbus 设备域名/IP
modbus port int 40635 Modbus TCP 端口
modbus slave_addr hex 0x01 Modbus 从机地址
modbus timeout_sec int 10 连接超时(秒)
modbus poll_ms int 2000 轮询间隔(毫秒)
storage interval_sec int 10 数据存储间隔(秒)
storage db_path string mppt_data.db SQLite 数据库文件路径
web port int 8085 HTTP 服务端口
web page_size int 50 历史数据默认分页大小

7.3 配置解析逻辑

  • 配置文件不存在时使用内置默认值,程序正常启动
  • 配置值解析失败时回退到默认值并输出警告日志
  • 十六进制值支持 0x 前缀(如 slave_addr = 0x01)

八、历史数据存储

8.1 数据库设计

使用 SQLite3 作为轻量级嵌入式数据库,无需额外服务进程。

history_data 表结构

CREATE TABLE IF NOT EXISTS history_data (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp       INTEGER NOT NULL,           -- Unix 时间戳
    battery_voltage REAL NOT NULL,              -- 电池电压 (V)
    pv_voltage      REAL NOT NULL,              -- 光伏电压 (V)
    pv_charge_current REAL NOT NULL,            -- 光伏充电电流 (A)
    cumulative_energy REAL NOT NULL,            -- 累计发电量 (kWh)
    machine_temp    REAL NOT NULL,              -- 机器温度 (°C)
    fault_code      INTEGER NOT NULL,           -- 故障代码
    battery_soc     INTEGER NOT NULL,           -- 电池电量 (%)
    battery_string  INTEGER NOT NULL,           -- 电池串数
    charge_state    INTEGER NOT NULL,           -- 充电状态
    device_type     INTEGER NOT NULL,           -- 设备类型
    pv_input_power  REAL NOT NULL,              -- 光伏输入功率 (W)
    created_at      TEXT DEFAULT (datetime('now', 'localtime'))
);

索引

CREATE INDEX IF NOT EXISTS idx_history_timestamp ON history_data(timestamp);

8.2 存储流程

Modbus 读取成功 → 更新共享数据 → 检查存储间隔
                                    │
                    ┌───────────────┴───────────────┐
                    │ 距上次存储 >= interval_sec?    │
                    │                               │
                    ▼ Yes                           ▼ No
              插入 SQLite                      跳过
              更新 last_store_time

8.3 查询 API

GET /api/history

参数 类型 必填 说明
start_time string 否 起始时间,格式 YYYY-MM-DDTHH:MM:SS 或 YYYY-MM-DD HH:MM:SS
end_time string 否 结束时间,格式同上
page int 否 页码,默认 1
page_size int 否 每页条数,默认取 config.web.page_size,最大 500
sort string 否 排序方式:ASC 或 DESC(默认)

响应示例:

{
  "total": 150,
  "page": 1,
  "page_size": 50,
  "total_pages": 3,
  "records": [
    {
      "id": 150,
      "timestamp": "2026-08-04 16:30:00",
      "timestamp_unix": 1785832200,
      "battery_voltage": 26.8,
      "pv_voltage": 37.2,
      "pv_charge_current": 2.2,
      "pv_input_power": 81.84,
      "cumulative_energy": 0.8,
      "machine_temp": 45,
      "fault_code": 20,
      "fault_desc": "Normal",
      "battery_soc": 96,
      "charge_state": 1,
      "charge_state_desc": "Charging (Bulk)",
      "device_type": 60
    }
  ]
}

九、安全考虑

方面 措施
网络隔离 Modbus TCP 仅出站连接,不监听外部端口
HTTP 访问 当前无认证,建议通过反向代理添加 Basic Auth
数据完整性 Modbus CRC-16 校验确保数据正确性
异常输入 HTTP 路由严格匹配,未匹配返回 404
内存安全 使用 cJSON 安全 API,无缓冲区溢出风险
SQL 注入 使用 SQLite3 参数化查询(? 占位符),杜绝注入风险
配置安全 config.ini 解析使用 snprintf 防溢出,非法值回退默认

十、扩展方向

  1. 告警通知: 温度过高、电压异常时发送邮件/微信通知
  2. HTTPS 支持: 已预留 OpenSSL 编译选项
  3. MQTT 上报: 将数据发布到 MQTT Broker 供其他系统消费
  4. 多设备支持: 支持同时监控多个 MPPT 控制器
  5. Web 认证: 添加 HTTP Basic Auth 或 Token 认证
  6. 数据导出: 支持 CSV/Excel 格式导出历史数据
  7. 统计聚合: 按小时/天/月聚合统计数据(平均、最大、最小)

最后更新: 2026-08-04 (v1.1.0)