Skip to main content

Formula Usage Guide

If you enter an arithmetic expression in the fomula field when registering a tag, the driver post-processes the raw value read from the PLC and uses the converted value for the cache / Sparkplug / REST responses.

ItemValue
Implementation classplantpulse.app.edge.component.collector.plc.PLCValueFomula
Call siteStep 3 of PLCValueReader.read() (raw → format → fomula → cache update)
Libraryplantpulse-edge-fomula.jar (plantpulse.edge.fomula.FormulaEngine)
Variable substitution${VALUE} (current tag value) / ${tag_id} (last value of another tag, LastValueMap cache)
Applicable data_typeApplied only to Float / Double / Integer / Long. Other types (String, Boolean, …) pass through unchanged
ResultBigDecimal of Formula.evaluate(...) → rendered per type (integer types as integers, Float/Double as decimals)

Processing Flow

  1. The driver receives the raw string value (e.g. "16384").

  2. PLCValueFomula.fomulaValue(address, value) is called.

  3. If address.getFomula() is empty, the value is returned as is.

  4. If data_type is not Float / Double / Integer / Long, the value is returned as is.

  5. The names referenced by the formula are resolved — VALUE is the value just read, the rest are the last values of other tags from LastValueMap.

  6. If a referenced tag has never been collected, an exception is raised:

    Variable value referenced by the formula is not yet in cache : formula=[`<fomula>`], variable=[`<name>`]
  7. Only the names referenced by the formula are bound to values and evaluated → BigDecimal → a string is returned per type.


Variable Types

NotationMeaningSource
${VALUE}Raw value of the current tagThe value just read by the driver
${TAG_ID_X}Last value of another tagLastValueMap (cache shared by all tags)

LastValueMap holds the last raw / post-processed value of every tag. You can reference not only other tags in the same OPC but also tags in other OPCs (provided that tag has been collected at least once and exists in the cache).

Variable Not Present

If you reference ${TAG_ZERO} but TAG_ZERO has never been collected, the value cannot be bound and an exception occurs. Set a shorter timecycle on the referenced tag, or register the referenced tag under auto_collect=true so it is collected first. (The syntax check performed at save time passes in this case — it is a collection-order issue, not a formula error.)


Expression Syntax

The syntax is the same as Excel. Function names and semantics match Excel, so you can port an Excel expression almost verbatim. Case is not significant (IF = if).

CategoryTokensNotes
Arithmetic+, -, *, /, ^^ is exponentiation
Comparison>, <, >=, <=, =, <>= is equal, <> is not equal (same as Excel)
Grouping(, )Parentheses take precedence
ConstantsPI, EPi, Euler's number
ConditionalIF, SWITCH, AND, OR, NOTIF(조건, 참일때, 거짓일때)
RoundingROUND, ROUNDUP, ROUNDDOWN, CEILING, FLOOR, INT, TRUNCROUND(값, 자리수)
NumericABS, SIGN, MOD, POWER, SQRT, CBRT, EXP, FACTThe sign of the remainder in MOD follows the divisor
AggregationMIN, MAX, SUM, AVERAGE, COALESCEMultiple arguments
LogarithmLOG, LOG10, LNLOG = common logarithm (base 10), LN = natural logarithm
TrigonometricSIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2Based on radians (same as Excel)
HyperbolicSINH, COSH, TANH, ASINH, ACOSH, ATANH
Angle conversionDEGREES, RADIANSRadians ↔ degrees
Not Supported
  • Bitwise operations (&, |)
  • User-defined functions
  • String operations / date operations If you need these, handle them in a later stage (external function / post-processing node).
Invalid Formulas Are Not Saved

A formula with invalid syntax is rejected at save time (UI save, CSV upload, and backup restore alike). You can enter a sample value in the tag settings modal to preview the result.

Failures during evaluation (division by zero, a referenced value that is not numeric, etc.) are recorded as a collection error and degrade the quality of that tag. A plausible-looking number is never stored in its place.

Variable Notation

Both ${VALUE} and the bare name VALUE are accepted. The same applies to other tags — ${TAG_ZERO} = TAG_ZERO. Existing saved ${...} formulas continue to work.

${VALUE}*0.1 기존 표기
VALUE*0.1 같은 뜻
IF(VALUE>100, 100, VALUE*0.1) 엑셀식

Extended Example Set

1. Simple Scaling (×0.1)

ItemValue
data_typeFloat
fomula${VALUE}*0.1
Input → Output163841638.4

Use case: integer raw → real number with 1 decimal place. For temperature / pressure / flow sensors that send values as integers.

2. Unit Conversion (mV → V)

ItemValue
data_typeFloat
fomula${VALUE}/1000
Input → Output33003.3

3. Offset Correction (Celsius → Kelvin)

ItemValue
data_typeFloat
fomula${VALUE}+273.15
Input → Output25298.15

4. Referencing Another Tag (Zero Correction)

ItemValue
data_typeFloat
fomula${VALUE}-${TAG_ZERO}
Input → OutputVALUE=1024, TAG_ZERO=241000

TAG_ZERO is the ID of another tag. Used for zero / tare correction.

5. Polynomial (Square)

ItemValue
data_typeFloat
fomula${VALUE}*${VALUE}*0.001
Input → Output10010.0

6. Function — Square Root

ItemValue
data_typeFloat
fomulasqrt(${VALUE})
Input → Output14412.0

7. Function — Trigonometric (sin, radians)

ItemValue
data_typeFloat
fomulasin(${VALUE})
Input → Output1.5708 (≈π/2) → 1.0

For degree input, use sin(${VALUE}*pi/180).

8. Multiple Tags — Calibration (gain × x + offset)

ItemValue
data_typeFloat
fomula${VALUE}*${TAG_GAIN}+${TAG_OFFSET}
Input → OutputVALUE=100, GAIN=0.05, OFFSET=27.0

A pattern in which per-device correction coefficients are managed as separate tags (or HTTP-bind tags).

9. Type Conversion / Forced Float

ItemValue
data_typeFloat
fomula${VALUE}*1.0
Input → Output123123.0

For when you only want to cast a value received as Integer into Float.

10. Exponentiation / Exponential

ItemValue
data_typeFloat
fomula${VALUE}^2
Input → Output525.0

11. Logarithm

ItemValue
data_typeFloat
fomulalog(${VALUE})
Input → Output1002.0

ln(...) is also available (natural logarithm).

12. Composite — dB Conversion of RMS

ItemValue
data_typeFloat
fomula20*log(${VALUE})
Input → Output100060.0

REST Registration Example (curl)

Registering a single tag:

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 적용"
}'

When registering an OPC together with a set of tags, include fomula inside tag_list[].

Reading a value (the post-processed value is returned):

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",
# ...
# }
# }

Common Errors and Fixes

Message / SymptomCauseFix
계산식에 해당하는 변수값이 아직 캐시에 없습니다 : 계산식=[${VALUE}-${TAG_ZERO}]The referenced tag (TAG_ZERO) has never been collectedRegister the referenced tag first and let it run one cycle at auto_collect=true
계산식이 참조하는 값이 숫자가 아닙니다The referenced tag's value is not numeric (e.g. referencing a tag that is data_type=String)Change it to reference a numeric-type tag
Closing brace not found / Missing second operandUnbalanced parentheses or a missing operand after an operatorRejected at save time, so the modal's error message pinpoints the position
Post-processing is not applied (raw value returned as is)data_type is String/BooleanChange it to Float/Double/Integer/Long, or use a separate processing stage if post-processing is required
Result is always 0The raw value really is 0Check the raw read value with GET /api/v1/tag/.../value first. (The old parser silently evaluated incomplete expressions such as ${VALUE}* as 0, but such expressions are now rejected at save time.)
Collection error FORMAT_FAILED + quality degradationEvaluation failure such as division by 0 or a negative sqrtInfinity/NaN is not stored; the failure is surfaced as an error instead. Add a guard to the formula — e.g. IF(TAG_ZERO=0, 0, VALUE/TAG_ZERO)

Behavioral Notes

  • LastValueMap is a singleton (in-memory). It is cleared when the gateway restarts → references to other tags may fail on the first cycle.
  • Evaluation uses BigDecimal throughout, so large integers in Long/QWord are preserved down to the last digit (no loss as in the old double evaluation).
  • The result is Double.toString() → cached as a string. It is cast back to data_type for display and Sparkplug publication (see Sparkplug DataTypeMapper).
  • If the formulas of several tags within the same OPC reference one another, the first cycle may partially fail depending on the collection order. This is not a significant operational problem (it works from the next cycle on), but for a clean setup place the referenced tags in a separate OPC with a shorter timecycle.