Skip to main content

Formula

When registering a tag, if you enter an arithmetic expression in the fomula field, the driver post-processes the raw value read from the PLC, and the converted value is used for the cache / Sparkplug / REST response.

ItemValue
Implementation classplantpulse.app.edge.component.collector.plc.PLCValueFomula
Call locationStep 3 of PLCValueReader.read() (raw → format → formula → 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_typeApplies to Float / Double / Integer / Long only. Other types (String, Boolean, …) pass through unchanged
ResultFormula.evaluate(...)'s BigDecimal → type-specific rendering (integer types render as integers, Float/Double as decimals)

Operation Flow​

  1. Receive a raw string value from the driver (e.g., "16384").

  2. Call PLCValueFomula.fomulaValue(address, value).

  3. If address.getFomula() is empty, return as-is.

  4. If data_type is not Float / Double / Integer / Long, return as-is.

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

  6. If a referenced tag has never been collected yet, throw an exception:

    Variable value referenced by the formula is not yet in cache : formula=[`<fomula>`], variable=[`<name>`]
  7. Bind only the names referenced by the formula with their values → evaluate via BigDecimal → return a type-specific string.


Variable Types​

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

LastValueMap stores the last raw / post-processed value of every tag. You can reference tags from another OPC as well as other tags within the same OPC (provided those tags have been collected at least once and exist in the cache).

Variable Not Found

If you reference ${TAG_ZERO} but TAG_ZERO has never been collected, no value can be bound, and an exception is raised. Use a shorter timecycle for the reference tag, or register it in auto_collect=true so it is collected first. (Syntax validation at save time passes this case — this is a collection-order issue, not a formula error.)


Expression Syntax​

Write it like Excel. Function names and meanings match Excel, so expressions you used in Excel can be ported almost as-is. Case is not sensitive (IF = if).

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

Formulas with syntax errors are rejected at save time (both UI save / CSV upload / backup restore). You can preview the result with a sample value in the tag settings modal.

A computation failure (division by zero, referenced value not numeric, etc.) is recorded as a collection error, and the quality of that tag degrades. No plausible number is stored instead.

Variable Notation​

Both ${VALUE} and bare name VALUE work. 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) 엑셀식

Rich Examples​

1. Simple scaling (×0.1)​

ItemValue
data_typeFloat
fomula${VALUE}*0.1
Input → Output16384 → 1638.4

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

2. Unit conversion (mV → V)​

ItemValue
data_typeFloat
fomula${VALUE}/1000
Input → Output3300 → 3.3

3. Offset correction (Celsius → Kelvin)​

ItemValue
data_typeFloat
fomula${VALUE}+273.15
Input → Output25 → 298.15

4. Referencing another tag (zero-point correction)​

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

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

5. Polynomial (square)​

ItemValue
data_typeFloat
fomula${VALUE}*${VALUE}*0.001
Input → Output100 → 10.0

6. Function — Square root​

ItemValue
data_typeFloat
fomulasqrt(${VALUE})
Input → Output144 → 12.0

7. Function — Trigonometric (sin, radian)​

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

If the input is in degrees, 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=2 → 7.0

A pattern for managing per-equipment calibration coefficients as separate tags (or HTTP-bind tags).

9. Type conversion / forcing to float​

ItemValue
data_typeFloat
fomula${VALUE}*1.0
Input → Output123 → 123.0

Use this when you only want to cast a value coming in as Integer to Float.

10. Exponentiation / Exponential​

ItemValue
data_typeFloat
fomula${VALUE}^2
Input → Output5 → 25.0

11. Logarithm​

ItemValue
data_typeFloat
fomulalog(${VALUE})
Input → Output100 → 2.0

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

12. Composite — RMS to dB conversion​

ItemValue
data_typeFloat
fomula20*log(${VALUE})
Input → Output1000 → 60.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 + tag bundle, you can include fomula inside tag_list[].

Value read (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 + Solutions​

Message / SymptomCauseSolution
계산식에 해당하는 변수값이 아직 캐시에 없습니다 : 계산식=[${VALUE}-${TAG_ZERO}]Referenced tag (TAG_ZERO) has never been collectedRegister the reference tag first + run a cycle with auto_collect=true
계산식이 참조하는 값이 숫자가 아닙니다Referenced tag's value is not numeric (e.g., referencing a tag that is data_type=String)Fix the reference to point to a numeric type tag
Closing brace not found / Missing second operandMismatched parentheses or missing operand after an operatorRejected at save time; the modal's error message tells you the location
Post-processing not applied (raw value returned)data_type is String/BooleanChange to Float/Double/Integer/Long, or use a separate processing stage if post-processing is required
Result is always 0The raw value is actually 0First verify the raw read value with GET /api/v1/tag/.../value. (The old parser silently computed incomplete formulas like ${VALUE}* as 0, but now such formulas are rejected at the save stage.)
Collection error FORMAT_FAILED + quality degradationComputation failure such as dividing by 0 or a negative sqrtInfinity/NaN are not stored; the error is surfaced. Add guards to the formula — e.g., IF(TAG_ZERO=0, 0, VALUE/TAG_ZERO)

Operational Notes​

  • LastValueMap is a singleton (in-memory). It is reset on gateway restart → the first cycle may fail for other-tag references.
  • Computation is all BigDecimal, so large integers in Long/QWord are preserved down to the last digit (no loss from the old double evaluation).
  • The result is Double.toString() → string cache. When displayed / published via Sparkplug, it is cast back to data_type (see Sparkplug DataTypeMapper).
  • If formulas for multiple tags within the same OPC reference each other, the first cycle may partially fail depending on the collection order. This is not a major operational issue (it works normally from the next cycle), but for a clean start, place the referenced tags in a separate OPC with a shorter timecycle.