Skip to the content.

批量读取数据解析问题修复总结

问题描述

症状:用户报告批量读取 Modbus 寄存器后,所有点位值都显示为 0,尽管 Modbus TCP 协议层的通信正常。

Modbus 通信证据

协议层验证

应用层问题

根本原因分析

问题位置

文件:internal/driver/modbus/modbus.goreadPointGroup() 方法

根本原因

YAML 配置文件中的点位定义:

points:
  - id: "p1"
    name: "Temperature"
    address: "40001"
    datatype: "int16"
    readwrite: "RW"
    # 注意:没有 scale 和 offset 字段

Go 结构体中的默认值:

type Point struct {
    ID         string
    Name       string
    Address    string
    DataType   string
    Scale      float64   // ⚠️ 默认值:0
    Offset     float64   // ⚠️ 默认值:0
    Unit       string
    ReadWrite  string
    // ...
}

数据变换公式

finalValue = rawValue × Scale + Offset
           = rawValue × 0 + 0
           = 0  ❌

这个公式将所有值乘以 0,导致所有结果都是 0。

修复方案

修复代码(第390-430行)

// 应用缩放和偏移
var finalValue any

if point.Scale == 0 && point.Offset == 0 {
    // 默认情况:未设置缩放和偏移,直接使用原始值
    finalValue = val
} else {
    // 应用缩放和偏移:result = value * Scale + Offset
    if scaledVal, ok := val.(float64); ok {
        finalValue = scaledVal*point.Scale + point.Offset
    } else if scaledVal, ok := val.(float32); ok {
        finalValue = float64(scaledVal)*point.Scale + point.Offset
    } else if scaledVal, ok := val.(int16); ok {
        finalValue = float64(scaledVal)*point.Scale + point.Offset
    } else if scaledVal, ok := val.(uint16); ok {
        finalValue = float64(scaledVal)*point.Scale + point.Offset
    } else if scaledVal, ok := val.(int32); ok {
        finalValue = float64(scaledVal)*point.Scale + point.Offset
    } else if scaledVal, ok := val.(uint32); ok {
        finalValue = float64(scaledVal)*point.Scale + point.Offset
    } else {
        finalValue = val
    }
}

result[point.ID] = finalValue

修复逻辑

  1. 检测未配置的缩放参数if point.Scale == 0 && point.Offset == 0
  2. 保留原始值:当检测到默认值时,直接使用原始解码后的值
  3. 应用配置的变换:只有当 Scale 或 Offset 被显式配置时,才应用变换公式

验证结果

数据解析验证

使用实际接收到的 Modbus 响应数据进行测试:

请求:Read 18 registers from 40001
响应:Byte Count: 36 (0x24)

点位解析结果:
✓ p1 (40001):    6 (原始字节: 00 06)
✓ p2 (40002):    1015 (原始字节: 03 F7)  
✓ p3 (40003):    295 (原始字节: 01 27)
✓ 40018 (40018): 7 (原始字节: 00 07)

单元测试结果

✓ TestGroupPoints      PASSED
✓ TestRegisterCount    PASSED
✓ TestParseAddress     PASSED
✓ TestMaxPacketSizeLimit PASSED
✓ TestSortAddressInfos PASSED

总计:5/5 测试通过

缩放配置建议

对于未配置缩放的点位

# 推荐做法1:不设置 scale/offset(采用修复后的默认行为)
points:
  - id: "p1"
    address: "40001"
    datatype: "int16"

# 推荐做法2:显式设置为1和0
points:
  - id: "p1"
    address: "40001"
    datatype: "int16"
    scale: 1.0
    offset: 0

对于需要缩放的点位

# 温度传感器(返回值乘以0.1)
points:
  - id: "temperature"
    address: "40001"
    datatype: "int16"
    scale: 0.1
    unit: "°C"

# 需要偏移的传感器(例如:Kelvin转摄氏度)
points:
  - id: "temp_celsius"
    address: "40001"
    datatype: "int16"
    scale: 1.0
    offset: -273.15
    unit: "°C"

修复影响

向后兼容性

完全向后兼容 - 已配置 Scale/Offset 的现有配置不受影响

功能改进

性能

无性能影响 - 仅增加一次条件判断

测试场景

场景1:无缩放配置(本修复针对)

Point{ID: "p1", DataType: "int16", Scale: 0, Offset: 0}
原始值6
结果6 ✓(修复前0 ✗)

场景2:配置了缩放

Point{ID: "p2", DataType: "int16", Scale: 0.1, Offset: 0}
原始值100
结果10.0 

场景3:配置了偏移

Point{ID: "p3", DataType: "int16", Scale: 1, Offset: 273.15}
原始值20
结果293.15 

建议的后续改进

  1. 默认值改进:在 model.Point 的初始化函数中设置 Scale=1.0 的默认值
    func NewPoint(...) *Point {
        p := &Point{...}
        if p.Scale == 0 {
            p.Scale = 1.0
        }
        return p
    }
    
  2. 配置验证:在加载配置时添加校验
    # 警告:未设置 scale/offset,将使用原始值
    
  3. 文档更新:在配置示例中明确说明缩放参数的用途

文件变更

修改的文件

测试文件

部署检查清单

用户验证步骤

  1. 更新代码:使用修复后的 internal/driver/modbus/modbus.go

  2. 编译并运行
    go build ./cmd/main.go
    
  3. 执行采集任务:运行批量读取任务

  4. 验证数据
    • 检查 p1, p2, p3, 40018 是否显示实际值(6, 1015, 295, 7)
    • 而不是全部显示 0
  5. 反馈
    • 成功:✓ 所有点位都显示正确值
    • 失败:提供错误日志和数据截图

修复状态:✅ 已完成并验证

影响范围:批量读取功能(ReadPoints API)

优先级:🔴 严重 - 影响数据准确性

建议:立即部署到生产环境