05 · 点、通道、设备:数据从哪来

源码:src/Hmi.Project/Model/ · 相关:第 07 章 驱动

什么时候你会需要它

  • 你要接一台新设备:得先建通道、再建设备、最后建点
  • 你要批量导入点表:得知道每个字段是什么意思
  • 你要写驱动插件:得知道驱动会收到什么样的配置
  • 你搞不清"内部变量"和"采集点"的区别(这是最常被搞混的一对概念)

三层关系:一条链上的三个层级

通道 Channel(一条通信链路:一个网口 / 一个串口)
  │
  ├── 设备 Device(从站 1:1号PLC,IP 192.168.1.10)
  │     ├── 点 Tag:温度
  │     ├── 点 Tag:压力
  │     └── 点 Tag:运行状态
  │
  ├── 设备 Device(从站 2:2号PLC,IP 192.168.1.11)
  │     └── 点 Tag:……
  │
  └── 设备 Device(从站 3:3号PLC)
        └── 点 Tag:……

通道 1 : N 设备 1 : N 点。 记住这个层级,后面所有配置都是按这个顺序建的。

用生活化的类比:

概念 类比 关键属性
通道 一根网线 / 一条电话线 扫描周期、超时、重试次数
设备 线那头的一个电话机 IP、端口、从站号
点 通话内容里的一个数字 地址、类型、量程、单位

通道:一条通信链路

一条通道 = 一个通信端口。它有:

字段 含义 默认值
Id 通道编号 —
Name 通道名(给人看的) —
DriverName 用哪个协议驱动,如 ModbusTcp —
ScanIntervalMs 多久扫一次 200 毫秒
TimeoutMs 单次请求多久算超时 1000 毫秒
RetryCount 失败重试几次 3
WriteQueueCapacity 挂起写请求总数上限;队列满时拒绝普通新写 4096
Devices 这条链路下的设备列表 —

⚠ 第一期是一通道一连接:一条通道同一时刻只连一个设备(取通道里第一个)。 也就是说"一条 Modbus TCP 通道挂 3 台 PLC"目前是轮流连,不是并发连。规划通道数量时要考虑这点。

设备:链路那头的从站

字段 含义
Id / Name 设备编号 / 设备名
Description 备注
StationAddress 从站号(Modbus 的 slave id / S7 的机架槽号)
Connection.Kind 连接方式:Tcp / Serial / Udp
Connection.Params 连接参数(见下)
Endpoint 端点,格式 host:port[,rack=0,slot=1]
ScanIntervalMs? 单独给这台设备设扫描周期(不填就用通道的)
RecoveryEnabled / RecoveryPeriodMs / MaxRecoveryMs 断线补采配置

连接参数(Connection.Params)按连接方式不同:

连接方式 参数
TCP host、port
串口 com、baud、parity、databits、stopbits
UDP host、port

💡 设备表单是数据驱动的:组态器打开设备弹窗时会先调 DescribeProtocol(通道的协议名), 拿到这个协议支持的连接方式和参数说明,再据此渲染表单。 所以新协议只要正确实现 IConnectionSchemaProvider,界面会自动长出对应的输入框,不用改前端。

点:最小的数据单元

一个点就是"一个值"。字段不少,按用途分四组:

身份组

字段 含义
Id 点编号。必须 0 起连续、不重复,跳号会导致工程校验失败
Name 点名,如 Boiler_Temp1。全局唯一,是脚本/界面引用它的方式
Type 数据类型:Bool / Int16 / Int32 / Float / Double / String 等
Desc 描述(给自己看的备注)
Unit 单位,如 ℃

位置组(采集点才有)

字段 含义
ChannelId 属于哪条通道
DeviceId 属于哪个设备
Address PLC 里的地址,如 40001 / DB1.DBD0 / D100

数值组

字段 含义
RawMin / RawMax PLC 里的原始范围
EngMin / EngMax 换算后的工程量范围
Decimals 显示保留几位小数
Deadband 死区(变化小于这个值不算变化)
ByteOrder 跨寄存器数值的字节序,默认 ABCD
Writable 能不能写
InitialValue 初始值

作用域组

字段 含义
IsInternal 是不是内部变量(见下)
Scope 作用域:全局 / 画面
ScreenName / LocalName 画面变量的宿主画面与局部名
LinkedVariable 联动变量

⭐ 内部变量 ≠ 采集点(最容易搞混的一对)

这两者存储结构完全一样(都是 TagDef),但概念必须分开:

采集点 内部变量
数据来源 外部 —— 从 PLC 经通道读上来 内部 —— 软件自己产生的中间量
有没有通道/设备/地址 有 没有
IsInternal false true
脚本能写吗 不能(被写门禁拦下) 能
界面上在哪配 点表页 全局变量页

⚠ 界面上这是两个独立的页面(点表 / 全局变量),不是同一个列表的两种过滤。 用户明确纠正过这一点,别做成一个页面。

为什么要有内部变量? 举个例:

采集点:  Boiler_Temp1      ← 从 PLC 读上来的真实温度
内部变量: Alarm.HighTemp   ← 脚本算出来的"是否高温",不来自 PLC

脚本可以写 Alarm.HighTemp,但不能写 Boiler_Temp1 —— 因为往采集点写值意味着要下发到 PLC, 那是运行态写值,被严格禁止(见第 12 章)。

一个完整的建点例子

假设要接一台 Modbus TCP 的温控仪表(IP 192.168.1.50,端口 502,从站号 1),读它的温度:

1. 建通道
   Name          = "车间Modbus"
   DriverName    = "ModbusTcp"
   ScanIntervalMs= 200

2. 在通道下建设备
   Name       = "温控仪表1"
   Endpoint   = "192.168.1.50:502"
   Connection = { Kind: "Tcp", Params: { host: "192.168.1.50", port: "502" } }
   StationAddress = 1

3. 在设备下建点
   Name      = "Oven.Temp"
   Type      = Float
   Address   = "40001"          ← Modbus 保持寄存器第 1 个
   RawMin/RawMax = 0 / 1000     ← 仪表给的是 0~1000 的整数
   EngMin/EngMax = 0 / 100.0    ← 实际是 0~100.0 ℃
   Unit      = "℃"
   Decimals  = 1
   Deadband  = 0.1

地址写法是最容易出错的地方 —— 不同协议完全不一样,详见第 08 章。

常见错误

现象 原因
工程校验失败 点 Id 跳号或重复。必须 0 起连续
设备连不上 Endpoint 和 Connection.Params 不一致,或者端口/从站号错
值一直是 0 或不变 地址写错,或者质量戳是 Bad(先看质量戳)
值大得离谱 RawMin/RawMax 没配,或者 ByteOrder 不对(跨寄存器的 32 位值)
界面狂刷新 没配 Deadband,传感器末位抖动被当成变化
脚本写不进去 你想写的是采集点 —— 采集点不允许脚本写,改用内部变量

小结

  • 层级:通道 1:N 设备 1:N 点
  • 通道 = 链路(扫描周期/超时/重试),设备 = 从站(IP/端口/从站号),点 = 数据(地址/类型/量程)
  • 内部变量和采集点是两回事,界面上也是两个独立页面
  • 点 Id 必须 0 起连续
  • 地址写法按协议不同,别照抄别的协议

下一步:第 06 章 工程文件与加载保存 —— 这些配置在磁盘上长什么样。