Skip to content

驱动(Drivers)

驱动是 CoreC 的南向抽象面,负责与工业设备通信。所有驱动实现统一的 core.Driver 接口,通过工厂模式自动注册,由调度器按周期触发读取。本章详解接口契约、能力声明与三种内置驱动的配置。

Driver 接口

core.Driver 是整个南向面的唯一抽象,类比 Clash 的 InboundListener

go
type Driver interface {
    // 生命周期
    Init(ctx context.Context, config DriverConfig) error
    Start(ctx context.Context) error
    Stop() error
    Restart(ctx context.Context, config DriverConfig) error

    // 数据操作
    Read(ctx context.Context, tags []string) ([]TagValue, error)
    Write(ctx context.Context, commands []WriteCommand) ([]WriteResult, error)
    Subscribe(ctx context.Context, tags []string) (<-chan DataPoint, error)

    // 元信息
    Name() string
    Type() string
    Status() DriverStatus
    Capabilities() DriverCapabilities
}

生命周期方法

方法调用时机职责
Init创建实例后解析配置、建立内部映射、不发起网络连接
Start引擎启动时发起连接;初始连接失败不返回错误,转入后台重连
Stop引擎停止 / 驱动移除时关闭连接、释放资源
Restart配置热更新时等价于 Stop → Init → Start

连接失败的容错策略

Start 阶段如果设备不可达,CoreC 不会返回错误导致启动失败,而是将状态置为 connecting 并启动后台 reconnectLoop 按指数退避重试(上限 30s)。这使得 CoreC 可以在设备离线时先启动,设备恢复后自动接入。

数据操作方法

Read —— 批量读取

go
Read(ctx context.Context, tags []string) ([]TagValue, error)

接收一组测点名,返回对应的 TagValue 列表。关键行为:

  • 批量语义:同周期的点位合并为一次调用,减少协议交互
  • 部分失败容忍:单个点位读取失败时,该点位返回 Quality: bad + Error不中断整批
  • 连接丢失感知:检测到连接类错误时,自动触发后台重连

Write —— 批量写入

go
Write(ctx context.Context, commands []WriteCommand) ([]WriteResult, error)

接收一组 WriteCommand,将值写入设备寄存器/线圈。每个命令独立返回 WriteResult{Success, Error}。写入由两种路径触发:

  • API POST /write —— 即时控制指令
  • 传输 OnCommand() 通道 —— 北向反向下发(如 MQTT Command Topic)

Subscribe —— 事件订阅

go
Subscribe(ctx context.Context, tags []string) (<-chan DataPoint, error)

对于支持事件驱动的协议(如 OPC UA Subscription 模式),返回一个数据点 channel,由驱动主动推送。不支持的驱动返回 ErrSubscribeNotSupported

能力声明(Capabilities)

每个驱动通过 Capabilities() 声明自身支持的操作,引擎与 API 层据此做能力协商:

go
type DriverCapabilities struct {
    CanRead      bool  // 是否支持读取
    CanWrite     bool  // 是否支持写入
    CanSubscribe bool  // 是否支持事件订阅
    BatchRead    bool  // 是否支持批量读取
    MaxBatchSize int   // 单次批量读取上限
}

内置驱动能力矩阵

驱动CanReadCanWriteCanSubscribeBatchReadMaxBatchSize
modbus-tcp125
s7220
opcua1000

MaxBatchSize 的来源

  • Modbus 125:单次 Modbus TCP 请求的寄存器上限(PDU 限制)
  • S7 220:标准 S7 请求的 PDU 字节上限
  • OPC UA 1000:客户端单次读取的 NodeID 推荐上限

点位配置(TagConfig)

每个驱动下定义一组点位(tag),描述"读什么、怎么读、多久读一次":

yaml
tags:
  - name: temperature        # 测点名(全局唯一标识,用于规则匹配与 API 查询)
    address: "40001"         # 协议地址(格式因驱动而异)
    type: float32            # 数据类型
    group: sensors           # 分组(用于规则匹配与主题渲染)
    interval: 1s             # 采集周期
    scale: 1.0               # 缩放系数(可选,value * scale + offset)
    offset: 0.0              # 偏移量(可选)
    deadband: 0.2            # 死区(可选,变化小于此值时不更新)
字段必填说明
name测点名,驱动内唯一
address协议地址,格式见各驱动说明
type数据类型,见下表
group分组名,用于规则匹配与主题模板(可选)
interval采集周期,Go duration 格式(1s500ms200ms
scale缩放系数,读取后执行 value * scale + offset(可选)
offset偏移量(可选)
deadband死区,抑制无意义的小幅波动上报(可选)

支持的数据类型

类型说明典型用途
bool布尔开关、状态、线圈
int8 / int16 / int32 / int64有符号整数计数器、字
uint8 / uint16 / uint32 / uint64无符号整数寄存器原始值
float32 / float64浮点数温度、压力、流量
string字符串OPC UA 字符串变量
bytes原始字节原始寄存器数据

内置驱动详解

Modbus TCP(modbus-tcp

Modbus 是工业领域最通用的协议,CoreC 实现了 TCP variant。

连接配置

yaml
- name: plc-modbus
  type: modbus-tcp
  settings:
    host: 192.168.1.100    # 设备 IP(必填)
    port: 502              # Modbus TCP 端口(默认 502)
    slave-id: 1            # 从站 ID(默认 1)
    timeout: 3s            # 读写超时(默认 3s)
    retry: 3               # 重试次数(默认 3)

地址格式

Modbus 地址通过前缀数字区分四个数据区:

地址范围数据区Modbus 功能码可读可写
00001 ~ 09999线圈(Coil)FC 01 / 05 / 15
10001 ~ 19999离散输入(Discrete Input)FC 02
30001 ~ 39999输入寄存器(Input Register)FC 04
40001 ~ 49999保持寄存器(Holding Register)FC 03 / 06 / 16

地址是 1-based

40001 对应保持寄存器 0(内部自动减 1 转为 0-based)。也支持直接写 0-based 裸地址(如 "0"),此时默认按保持寄存器处理。

配置示例

yaml
tags:
  - name: temperature
    address: "40001"      # 保持寄存器 0,float32
    type: float32
    group: sensors
    interval: 1s
    deadband: 0.2
  - name: pressure
    address: "40003"      # 保持寄存器 2,float32
    type: float32
    group: sensors
    interval: 1s
  - name: pump_status
    address: "00001"      # 线圈 0,bool
    type: bool
    group: actuators
    interval: 2s

Siemens S7(s7

支持 S7-200/300/400/1200/1500 全系列,基于 ISO-on-TCP(端口 102)。

连接配置

yaml
- name: siemens-s7-300
  type: s7
  settings:
    host: 192.168.1.200   # PLC IP(必填)
    port: 102             # ISO-on-TCP 端口(默认 102)
    rack: 0               # 机架号(默认 0)
    slot: 2               # 槽号:S7-300 用 2,S7-1200/1500 用 1
    timeout: 3s           # 超时(默认 5s)

slot 取值

不同 S7 系列的 CPU 槽号不同:

  • S7-300slot: 2(默认)
  • S7-1200 / S7-1500slot: 1 配置错误会导致连接失败。

地址格式

S7 地址支持四种数据区,格式丰富。每个区都支持位(X)、字节(B)、字(W)、双字(D)四种宽度:

格式数据区示例说明
DB{n}.DB{X/B/W/D}{byte}数据块DB1.DBD0DB1 双字(float32)起始字节 0
DB1.DBW4DB1 字(uint16)起始字节 4
DB1.DBB2DB1 字节(uint8)起始字节 2
DB1.DBX0.0DB1 位,字节 0 位 0
M{B/W/D}{byte}M{byte}.{bit}Merker(M)M10.0标志位(位寻址)
MB10M 字节(uint8)起始字节 10
MW20M 字(uint16)起始字节 20
MD30M 双字(float32)起始字节 30
[IE]{B/W/D}{byte}[IE]{byte}.{bit}输入(I/E)I0.0输入位(位寻址)
IB1 / EB1输入字节(uint8)
IW2 / EW2输入字(uint16)
ID4 / ED4输入双字(uint32)
[QA]{B/W/D}{byte}[QA]{byte}.{bit}输出(Q/A)Q0.0输出位(位寻址)
QB1 / AB1输出字节(uint8)
QW2 / AW2输出字(uint16)
QD4 / AD4输出双字(uint32)

配置示例

yaml
tags:
  - name: reactor_temp
    address: "DB1.DBD0"   # DB1 双字 0,float32
    type: float32
    group: reactor
    interval: 500ms
  - name: motor_speed
    address: "DB1.DBW4"   # DB1 字 4,uint16
    type: uint16
    group: motors
    interval: 1s
  - name: emergency_stop
    address: "I0.0"       # 输入位 0.0,bool
    type: bool
    group: safety
    interval: 200ms

OPC UA(opcua

支持标准 NodeID,提供轮询与事件订阅两种模式。

连接配置

yaml
- name: opc-server
  type: opcua
  settings:
    endpoint: "opc.tcp://192.168.1.50:4840"   # 端点(必填)
    mode: polling                              # polling 或 subscription
    timeout: 5s                                # 超时(默认 5s)
    subscription-interval: 500ms               # 订阅模式采样间隔
    security-policy: "None"                    # 安全策略(可选)
    security-mode: "None"                      # 安全模式(可选)
    username: ""                               # 用户名认证(可选)
    password: ""                               # 密码认证(可选)
    cert-file: ""                              # 客户端证书(可选)
    key-file: ""                               # 客户端私钥(可选)

两种采集模式

模式说明适用场景
polling调度器按 interval 周期主动读取通用场景,行为可预测
subscriptionOPC UA 服务端主动推送变更高实时性、低流量场景

subscription 模式

mode: subscription 时,驱动调用 Subscribe 建立服务端订阅,由 OPC UA Server 在数据变化时主动推送,CoreC 无需周期轮询。此时点位的 interval 字段不再用于调度触发,而由 subscription-interval 控制服务端采样间隔。

地址格式(NodeID)

OPC UA 使用 NodeID 寻址,支持两种形式:

格式示例说明
命名空间 + 字符串ns=2;s=Conveyor.Speed命名空间 2,字符串标识
命名空间 + 数字ns=1;i=1001命名空间 1,数字标识

配置示例

yaml
tags:
  - name: conveyor_speed
    address: "ns=2;s=Conveyor.Speed"
    type: float64
    group: conveyor
    interval: 1s
  - name: batch_count
    address: "ns=1;i=1001"
    type: uint32
    group: production
    interval: 2s

连接状态机

所有驱动遵循统一的状态机:

                 Init


          ┌───────────────┐  connect OK   ┌────────────┐
          │ disconnected  │ ────────────▶ │ connected  │
          └───────────────┘               └─────┬──────┘
                  │                             │
            Start │                             │ 连接断开
                  ▼                             ▼
          ┌───────────────┐  reconnect    ┌────────────┐
          │  connecting   │ ◀─────────── │   error    │
          └───────────────┘              └────────────┘

          connect │ OK

            ┌────────────┐
            │ connected  │
            └────────────┘

状态可通过 GET /drivers/{name} API 的 state 字段查询,取值为 disconnected / connecting / connected / error

驱动状态查询

bash
curl -s -H "Authorization: Bearer $TOKEN" \
  http://localhost:9090/drivers/plc-modbus | jq
json
{
  "name": "plc-modbus",
  "type": "modbus-tcp",
  "state": "connected",
  "last_read": "2024-01-15T10:30:15Z",
  "last_error": "",
  "tag_count": 3,
  "read_count": 15234,
  "error_count": 2
}
字段说明
state连接状态
last_read最后一次成功读取时刻
last_error最后一条错误信息
tag_count配置的点位数
read_count累计读取次数
error_count累计错误次数

编写自定义驱动

实现一个新协议只需三步:

go
package myprotocol

import "github.com/lsy1291455142/CoreC/core"

// 1. 实现 core.Driver 接口
type MyDriver struct { /* ... */ }

func NewMyDriver(config core.DriverConfig) (core.Driver, error) {
    return &MyDriver{}, nil
}

// 实现 Init/Start/Stop/Restart/Read/Write/Subscribe/Name/Type/Status/Capabilities ...

// 2. 在 init() 中注册工厂
func init() {
    core.RegisterDriver("my-protocol", NewMyDriver)
}
go
// 3. 在 main.go 中导入你的驱动包
import _ "github.com/yourorg/corec-driver-myprotocol"

工厂注册模式

CoreC 使用全局 Registrycore/registry.go)管理驱动与传输工厂。RegisterDriverinit() 中调用,CreateDriver 在引擎启动时按 type 字段查找工厂并实例化。这与 Clash 的代理注册机制完全一致。

下一步

  • 传输 —— 数据采集后的北向目的地
  • 规则引擎 —— 控制哪些数据发往哪个传输
  • 数据流 —— 回顾驱动在整个管道中的位置

Released under the MIT License.