Skip to the content.

多从属设备轮询实现指南

概述

本指南说明如何实现在单一 TCP 连接上轮询读取多个 Modbus 从属设备(Slave)。这对于需要与多台从属设备通信的网关应用非常有用。

核心特性

配置格式

旧格式(单设备模式 - 仍然支持)

devices:
  - id: "device-1"
    name: "Single Device"
    protocol: "modbus-tcp"
    interval: 2s
    enable: true
    config:
      url: "tcp://127.0.0.1:502"
      slave_id: 1
    points:
      - id: "p1"
        name: "Temperature"
        address: "40001"
        datatype: "int16"

新格式(多从属设备模式 - 新增)

devices:
  - id: "gateway-1"
    name: "Multi-Slave Gateway"
    protocol: "modbus-tcp"
    interval: 2s
    enable: true
    config:
      url: "tcp://127.0.0.1:502"
      # 注意:不再在 config 中设置 slave_id
    
    # 新增:slaves 数组定义多个从属设备
    slaves:
      - slave_id: 1
        enable: true
        points:
          - id: "p1"
            name: "Temperature"
            address: "40001"
            datatype: "int16"
      
      - slave_id: 6
        enable: true
        points:
          - id: "p2"
            name: "Humidity"
            address: "40002"
            datatype: "int16"

工作流程

轮询周期(interval=2s)
│
├─ 连接设备 (首次)
│
├─ Slave 1 轮询
│  ├─ 设置 Unit ID = 1
│  ├─ 批量读取 points
│  ├─ 解析数据
│  └─ 发送到 Pipeline
│
├─ Slave 6 轮询
│  ├─ 设置 Unit ID = 6
│  ├─ 批量读取 points
│  ├─ 解析数据
│  └─ 发送到 Pipeline
│
└─ 下一轮询周期

实现细节

1. 模型定义(model/types.go)

新增 SlaveDevice 结构体:

type SlaveDevice struct {
    SlaveID uint8     // Modbus slave ID
    Points  []Point   // Points for this slave
    Enable  bool      // Whether this slave is enabled
}

type Device struct {
    // ... 现有字段 ...
    Points  []Point        // 单设备模式使用
    Slaves  []SlaveDevice  // 多设备模式使用
}

关键点

2. 驱动接口扩展(driver/interface.go)

新增方法:

type Driver interface {
    // ... 现有方法 ...
    // SetSlaveID 为支持多从属设备的协议设置 slave/unit ID
    SetSlaveID(slaveID uint8) error
}

3. Modbus 驱动实现(driver/modbus/modbus.go)

SetSlaveID 实现

func (d *ModbusDriver) SetSlaveID(slaveID uint8) error {
    if !d.connected || d.client == nil {
        return fmt.Errorf("driver not connected")
    }
    d.client.SetUnitId(slaveID)
    log.Printf("ModbusDriver SetSlaveID: changed to %d", slaveID)
    return nil
}

批量读取多 Slave

func (d *ModbusDriver) ReadMultipleSlaves(ctx context.Context, 
    slaves []model.SlaveDevice, deviceID string) (map[string]model.Value, error) {
    // 遍历每个从属设备
    // 为每个 slave 设置 Unit ID
    // 执行批量读取
    // 合并结果
}

4. 设备管理器更新(core/device_manager.go)

collect 方法增强

func (dm *DeviceManager) collect(dev *model.Device, d drv.Driver, node *DeviceNodeTemplate) {
    if len(dev.Slaves) > 0 {
        // 多从属设备模式
        for _, slave := range dev.Slaves {
            if !slave.Enable {
                continue
            }
            // 为该 slave 读取数据
            dm.readPointsForSlave(d, slave.SlaveID, slave.Points, ctx)
        }
    } else {
        // 单设备模式(兼容旧代码)
        d.ReadPoints(ctx, dev.Points)
    }
}

辅助方法

func (dm *DeviceManager) readPointsForSlave(d drv.Driver, slaveID uint8, 
    points []model.Point, ctx context.Context) (map[string]model.Value, error) {
    // 设置 slave_id
    err := d.SetSlaveID(slaveID)
    // 读取该 slave 的点位
    return d.ReadPoints(ctx, points)
}

配置示例

完整的多 Slave 配置

config_multi_slave.yaml

关键配置点:

性能特性

优化

  1. 连接复用:多个 Slave 共享单一 TCP 连接
    • 减少网络开销
    • 降低内存占用
    • 简化连接管理
  2. 批量读取:每个 Slave 内部使用批量读取
    • 每个轮询周期减少 Modbus 请求次数
    • 例如:18 个点位可能只需 2-5 次请求
  3. 轮询顺序:按配置文件中的 Slave 顺序轮询
    • Slave 1 → Slave 6 → Slave 10(如果启用)
    • 可预测的读取模式

性能示例

假设配置 3 个 Slave,每个 Slave 18 个点位:

未优化方式

使用本实现

状态管理

集成状态机

每个设备(而非单个 Slave)有一个状态:

故障处理

如果某个 Slave 读取失败:

扩展功能

可选功能1:Slave 级状态管理

type SlaveDevice struct {
    SlaveID   uint8
    Points    []Point
    Enable    bool
    // 可选:独立的状态追踪
    Runtime   *SlaveRuntime
}

type SlaveRuntime struct {
    FailCount     int
    SuccessCount  int
    LastFailTime  time.Time
}

可选功能2:动态启用/禁用

// 运行时动态启用或禁用某个 Slave
func (dm *DeviceManager) SetSlaveEnabled(deviceID string, slaveID uint8, enabled bool) error {
    // 修改 dev.Slaves[].Enable
}

可选功能3:优先级读取

// 根据优先级而非配置顺序读取
type SlaveDevice struct {
    SlaveID   uint8
    Points    []Point
    Enable    bool
    Priority  int  // 高优先级先读取
}

迁移指南

从单设备到多 Slave

步骤 1:使用新配置格式

# 旧格式
devices:
  - id: "gw1"
    config:
      slave_id: 1
    points: [...]

# 新格式
devices:
  - id: "gw1"
    config:
      # 移除 slave_id
    slaves:
      - slave_id: 1
        points: [...]

步骤 2:重启网关

步骤 3:验证数据采集正常

兼容性

测试

单元测试

// internal/core/device_manager_test.go
func TestMultiSlaveCollection(t *testing.T) {
    // 创建多 Slave 设备
    // 模拟 Modbus 响应
    // 验证轮询和数据合并
}

集成测试

# 启动网关
./gateway -config config_multi_slave.yaml

# 查看日志
# Device gateway-1 using multi-slave mode (3 slaves)
# Switched to slave_id: 1
# Switched to slave_id: 6
# Switched to slave_id: 10

故障排查

问题1:某个 Slave 的数据不更新

原因

解决

问题2:数据解析错误(全为 0)

原因

解决

问题3:性能下降

原因

解决

文件变更

文件 变更 说明
internal/model/types.go 新增 SlaveDevice 结构体 支持多从属设备配置
internal/driver/interface.go 新增 SetSlaveID() 方法 通用驱动接口
internal/driver/modbus/modbus.go 新增 SetSlaveID() 和 ReadMultipleSlaves() Modbus 驱动实现
internal/core/device_manager.go 增强 collect() 方法,新增 readPointsForSlave() 设备管理器支持
config_multi_slave.yaml 新增 配置文件示例

总结

这个实现提供了:

  1. 清晰的架构:多 Slave 支持通过配置而非代码更改
  2. 高效的资源使用:连接和批量读取的充分利用
  3. 良好的可维护性:分离关注点,易于扩展
  4. 完全的向后兼容性:现有代码无需修改
  5. 灵活的扩展空间:支持多种高级特性

相关文档