计算公式 (Formula)
在注册标签时,将算术公式填入 fomula 字段,驱动程序会对从 PLC 读取的原始值(raw value)进行后处理,并将转换后的值用于缓存 / Sparkplug / REST 响应。
| 项目 | 值 |
|---|---|
| 实现类 | plantpulse.app.edge.component.collector.plc.PLCValueFomula |
| 调用位置 | PLCValueReader.read() 的第 3 步(raw → format → formula → 缓存更新) |
| 库 | plantpulse-edge-fomula.jar (plantpulse.edge.fomula.FormulaEngine) |
| 变量替换 | ${VALUE} (当前标签值) / ${tag_id} (其他标签的最后值, LastValueMap 缓存) |
应用对象 data_type | 仅适用于 Float / Double / Integer / Long。其他类型(String, Boolean, …)则直接通过 |
| 结果 | Formula.evaluate(...) 中的 BigDecimal → 按类型渲染(整数类型显示为整数,Float/Double 显示为小数) |
操作流程
-
从驱动程序接收 raw 字符串值(例如:
"16384")。 -
调用
PLCValueFomula.fomulaValue(address, value)。 -
如果
address.getFomula()为空,则直接返回。 -
如果
data_type不是 Float / Double / Integer / Long,则直接返回。 -
检查公式引用的名称 —
VALUE是刚读取的值,其余是LastValueMap中其他标签的最后值。 -
如果引用的标签尚未被采集过,则抛出异常:
Variable value referenced by the formula is not yet in cache : formula=[`<fomula>`], variable=[`<name>`] -
仅将公式引用的名称绑定值后进行求值 →
BigDecimal→ 返回按类型转换的字符串。
变量类型
| 表示法 | 含义 | 来源 |
|---|---|---|
${VALUE} | 当前标签的 raw 值 | 驱动程序刚刚读取的值 |
${TAG_ID_X} | 其他标签的最后值 | LastValueMap (全局标签共用缓存) |
LastValueMap 保存所有标签的最后 raw / 后处理值。不仅可以引用同一 OPC 的其他标签,还可以引用其他 OPC 的标签(前提是该标签已被采集过并存在于缓存中)。
如果引用了 ${TAG_ZERO},而 TAG_ZERO 尚未被采集过,则无法绑定值,从而引发异常。请缩短引用标签的 timecycle,或通过配置 auto_collect=true 使引用标签先被采集。(保存时的语法检查会允许这种情况 —— 这是采集顺序问题,而非公式错误。)
表达式语法
写法与 Excel 相同。 函数名称和含义与 Excel 一致,因此几乎可以直接沿用 Excel 中的公式。不区分大小写(IF = if)。
| 类别 | 运算符/函数 | 备注 |
|---|---|---|
| 算术 | +, -, *, /, ^ | ^ 为幂运算 |
| 比较 | >, <, >=, <=, =, <> | = 表示相等, <> 表示不等(与 Excel 相同) |
| 分组 | (, ) | 括号优先 |
| 常量 | PI, E | 圆周率, 自然常数 |
| 条件 | IF, SWITCH, AND, OR, NOT | IF(조건, 참일때, 거짓일때) |
| 舍入 | ROUND, ROUNDUP, ROUNDDOWN, CEILING, FLOOR, INT, TRUNC | ROUND(값, 자리수) |
| 数值 | ABS, SIGN, MOD, POWER, SQRT, CBRT, EXP, FACT | MOD 的余数符号与被除数一致 |
| 聚合 | MIN, MAX, SUM, AVERAGE, COALESCE | 多个参数 |
| 对数 | LOG, LOG10, LN | LOG = 常用对数(以10为底), LN = 自然对数 |
| 三角 | SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2 | 以弧度为单位(与 Excel 相同) |
| 双曲 | SINH, COSH, TANH, ASINH, ACOSH, ATANH | |
| 角度转换 | DEGREES, RADIANS | 弧度 ↔ 角度 |
- 位运算(
&,|) - 用户自定义函数
- 字符串运算 / 日期运算 如有此类需求,请在后续步骤(外部函数 / 后处理节点)中处理。
语法不正确的计算公式在保存时会被拒绝(UI 保存 / CSV 上传 / 备份恢复均适用)。 可以在标签设置对话框中输入示例值,预先查看计算结果。
计算过程中的失败(除零、引用值非数字等)会被记录为采集错误,并降低该标签的质量。不会用看似合理的数值来替代。
变量表示法
同时使用 ${VALUE} 和简称 VALUE。其他标签也是如此 —
${TAG_ZERO} = TAG_ZERO。已保存的 ${...} 计算公式仍可正常工作。
${VALUE}*0.1 기존 표기
VALUE*0.1 같은 뜻
IF(VALUE>100, 100, VALUE*0.1) 엑셀식
丰富的示例集
1. 简单缩放(×0.1)
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | ${VALUE}*0.1 |
| 输入 → 输出 | 16384 → 1638.4 |
用途:整数 raw → 保留一位小数的实数。当温度/压力/流量传感器发送整数值时使用。
2. 单位换算(mV → V)
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | ${VALUE}/1000 |
| 输入 → 输出 | 3300 → 3.3 |
3. 偏移修正(摄氏度 → 开尔文)
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | ${VALUE}+273.15 |
| 输入 → 输出 | 25 → 298.15 |
4. 引用其他标签(零点修正)
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | ${VALUE}-${TAG_ZERO} |
| 输入 → 输出 | VALUE=1024, TAG_ZERO=24 → 1000 |
TAG_ZERO 是其他标签的 ID。用于零点 / 去皮修正。
5. 多项式(平方)
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | ${VALUE}*${VALUE}*0.001 |
| 输入 → 输出 | 100 → 10.0 |
6. 函数 — 平方根
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | sqrt(${VALUE}) |
| 输入 → 输出 | 144 → 12.0 |
7. 函数 — 三角(sin, 弧度)
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | sin(${VALUE}) |
| 输入 → 输出 | 1.5708 (≈π/2) → 1.0 |
如果输入是角度(degree),请使用 sin(${VALUE}*pi/180)。
8. 多标签 — 校准(增益 × x + 偏移)
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | ${VALUE}*${TAG_GAIN}+${TAG_OFFSET} |
| 输入 → 输出 | VALUE=100, GAIN=0.05, OFFSET=2 → 7.0 |
这是一种按设备管理校准系数的模式,可将系数存储在单独的标签(或 HTTP-bind 标签)中。
9. 类型转换 / 强制转换为浮点数
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | ${VALUE}*1.0 |
| 输入 → 输出 | 123 → 123.0 |
仅想将 Integer 传入的值强制转换为 Float 时使用。
10. 幂 / 指数
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | ${VALUE}^2 |
| 输入 → 输出 | 5 → 25.0 |
11. 对数
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | log(${VALUE}) |
| 输入 → 输出 | 100 → 2.0 |
也可使用 ln(...) (自然对数)。
12. 复合 — RMS 的 dB 转换
| 项目 | 值 |
|---|---|
data_type | Float |
fomula | 20*log(${VALUE}) |
| 输入 → 输出 | 1000 → 60.0 |
REST 注册示例(curl)
单标签注册:
curl -X POST http://<edge-host>/api/v1/opc/OPC_LS_XBM_0001/tag \
-H "Content-Type: application/json" \
-d '{
"tag_id": "OPC_LS_XBM_0001_TAG_PRESS",
"tag_name": "Pressure (kPa)",
"plc_address": "D00100",
"data_type": "Float",
"format": "REAL",
"fomula": "${VALUE}*0.1",
"description": "스케일 ×0.1 적용"
}'
注册 OPC + 标签组时,在 tag_list[] 中放入 fomula 即可。
读取值(返回后处理后的值):
curl -s http://<edge-host>/api/v1/tag/OPC_LS_XBM_0001_TAG_PRESS/value | jq
# {
# "result": "OK",
# "data": {
# "tag_id": "OPC_LS_XBM_0001_TAG_PRESS",
# "value": "1638.4",
# ...
# }
# }
常见错误 + 解决方案
| 消息 / 症状 | 原因 | 解决方案 |
|---|---|---|
계산식에 해당하는 변수값이 아직 캐시에 없습니다 : 계산식=[${VALUE}-${TAG_ZERO}] | 引用的标签(TAG_ZERO)从未被采集过 | 先注册引用标签,并使用 auto_collect=true 运行一个周期 |
계산식이 참조하는 값이 숫자가 아닙니다 | 引用标签的值不是数字(例如:引用了 data_type=String 类型的标签) | 修改为引用数字类型的标签 |
Closing brace not found / Missing second operand | 括号不匹配或运算符后缺少操作数 | 保存时会被拒绝,请查看对话框的错误消息,它会指出具体位置 |
| 后处理未生效(返回原始值) | data_type 是 String/Boolean | 将其改为 Float/Double/Integer/Long,或在需要后处理时使用单独的加工步骤 |
结果始终为 0 | raw 值实际上就是 0 | 先用 GET /api/v1/tag/.../value 确认 raw 读取值。(旧解析器会将 ${VALUE}* 这类不完整的公式静默计算为 0,但现在这类公式在保存阶段就会被拒绝。) |
采集错误 FORMAT_FAILED + 质量下降 | 除以 0 或对 sqrt 开平方根等计算失败 | 不保存 Infinity/NaN,而是以错误形式暴露。请在公式中添加保护逻辑 — 例如:IF(TAG_ZERO=0, 0, VALUE/TAG_ZERO) |
操作备忘
LastValueMap是单例(内存中)。网关重启时会被初始化 → 第一个周期可能会无法引用其他标签。- 计算全程使用
BigDecimal,因此Long/QWord的大整数也能保留到最后一位(不会出现旧版double求值中的精度损失)。 - 结果在
Double.toString()→ 字符串缓存中。在界面 / Sparkplug 发布时,会再次转换为data_type(请参考 SparkplugDataTypeMapper)。 - 如果同一 OPC 内的多个标签的公式相互引用,则采集顺序会导致第一个周期部分失败。这在运营上不是大问题(从下一个周期开始会恢复正常),但若想避免,请将引用目标标签放在保留周期更短的
timecycle的独立 OPC 中。