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.
| Item | Value |
|---|---|
| Implementation class | plantpulse.app.edge.component.collector.plc.PLCValueFomula |
| Call location | Step 3 of PLCValueReader.read() (raw → format → formula → cache update) |
| Library | plantpulse-edge-fomula.jar (plantpulse.edge.fomula.FormulaEngine) |
| Variable substitution | ${VALUE} (current tag value) / ${tag_id} (last value of another tag, LastValueMap cache) |
Applicable data_type | Applies to Float / Double / Integer / Long only. Other types (String, Boolean, …) pass through unchanged |
| Result | Formula.evaluate(...)'s BigDecimal → type-specific rendering (integer types render as integers, Float/Double as decimals) |
Operation Flow
-
Receive a raw string value from the driver (e.g.,
"16384"). -
Call
PLCValueFomula.fomulaValue(address, value). -
If
address.getFomula()is empty, return as-is. -
If
data_typeis not Float / Double / Integer / Long, return as-is. -
Check the names referenced by the formula —
VALUEis the value just read, the rest are the last values of other tags inLastValueMap. -
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>`] -
Bind only the names referenced by the formula with their values → evaluate via
BigDecimal→ return a type-specific string.
Variable Types
| Notation | Meaning | Source |
|---|---|---|
${VALUE} | Raw value of the current tag | Value just read by the driver |
${TAG_ID_X} | Last value of another tag | LastValueMap (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).
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).
| Category | Token | Notes |
|---|---|---|
| Arithmetic | +, -, *, /, ^ | ^ is exponentiation |
| Comparison | >, <, >=, <=, =, <> | = is equal, <> is not equal (same as Excel) |
| Grouping | (, ) | Parentheses take precedence |
| Constants | PI, E | Pi, e |
| Conditional | IF, SWITCH, AND, OR, NOT | IF(조건, 참일때, 거짓일때) |
| Rounding | ROUND, ROUNDUP, ROUNDDOWN, CEILING, FLOOR, INT, TRUNC | ROUND(값, 자리수) |
| Numeric | ABS, SIGN, MOD, POWER, SQRT, CBRT, EXP, FACT | For MOD, the sign of the remainder follows the divisor |
| Aggregation | MIN, MAX, SUM, AVERAGE, COALESCE | Multiple arguments |
| Logarithm | LOG, LOG10, LN | LOG = common log (base 10), LN = natural log |
| Trigonometric | SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2 | Radian basis (same as Excel) |
| Hyperbolic | SINH, COSH, TANH, ASINH, ACOSH, ATANH | |
| Angle conversion | DEGREES, RADIANS | Radian ↔ degrees |
- Bitwise operations (
&,|) - User-defined functions
- String operations / date operations If you need these, handle them in a later stage (external functions / post-processing nodes).
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)
| Item | Value |
|---|---|
data_type | Float |
fomula | ${VALUE}*0.1 |
| Input → Output | 16384 → 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)
| Item | Value |
|---|---|
data_type | Float |
fomula | ${VALUE}/1000 |
| Input → Output | 3300 → 3.3 |
3. Offset correction (Celsius → Kelvin)
| Item | Value |
|---|---|
data_type | Float |
fomula | ${VALUE}+273.15 |
| Input → Output | 25 → 298.15 |
4. Referencing another tag (zero-point correction)
| Item | Value |
|---|---|
data_type | Float |
fomula | ${VALUE}-${TAG_ZERO} |
| Input → Output | VALUE=1024, TAG_ZERO=24 → 1000 |
TAG_ZERO is the ID of another tag. Used for zero-point / tare correction.
5. Polynomial (square)
| Item | Value |
|---|---|
data_type | Float |
fomula | ${VALUE}*${VALUE}*0.001 |
| Input → Output | 100 → 10.0 |
6. Function — Square root
| Item | Value |
|---|---|
data_type | Float |
fomula | sqrt(${VALUE}) |
| Input → Output | 144 → 12.0 |
7. Function — Trigonometric (sin, radian)
| Item | Value |
|---|---|
data_type | Float |
fomula | sin(${VALUE}) |
| Input → Output | 1.5708 (≈π/2) → 1.0 |
If the input is in degrees, use sin(${VALUE}*pi/180).
8. Multiple tags — Calibration (gain × x + offset)
| Item | Value |
|---|---|
data_type | Float |
fomula | ${VALUE}*${TAG_GAIN}+${TAG_OFFSET} |
| Input → Output | VALUE=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
| Item | Value |
|---|---|
data_type | Float |
fomula | ${VALUE}*1.0 |
| Input → Output | 123 → 123.0 |
Use this when you only want to cast a value coming in as Integer to Float.
10. Exponentiation / Exponential
| Item | Value |
|---|---|
data_type | Float |
fomula | ${VALUE}^2 |
| Input → Output | 5 → 25.0 |
11. Logarithm
| Item | Value |
|---|---|
data_type | Float |
fomula | log(${VALUE}) |
| Input → Output | 100 → 2.0 |
ln(...) is also available (natural log).
12. Composite — RMS to dB conversion
| Item | Value |
|---|---|
data_type | Float |
fomula | 20*log(${VALUE}) |
| Input → Output | 1000 → 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 / Symptom | Cause | Solution |
|---|---|---|
계산식에 해당하는 변수값이 아직 캐시에 없습니다 : 계산식=[${VALUE}-${TAG_ZERO}] | Referenced tag (TAG_ZERO) has never been collected | Register 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 operand | Mismatched parentheses or missing operand after an operator | Rejected at save time; the modal's error message tells you the location |
| Post-processing not applied (raw value returned) | data_type is String/Boolean | Change to Float/Double/Integer/Long, or use a separate processing stage if post-processing is required |
Result is always 0 | The raw value is actually 0 | First 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 degradation | Computation failure such as dividing by 0 or a negative sqrt | Infinity/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
LastValueMapis 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 inLong/QWordare preserved down to the last digit (no loss from the olddoubleevaluation). - The result is
Double.toString()→ string cache. When displayed / published via Sparkplug, it is cast back todata_type(see SparkplugDataTypeMapper). - 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.