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 章 工程文件与加载保存 —— 这些配置在磁盘上长什么样。