본문으로 건너뛰기

Formula (계산식, fomula) 사용 가이드

태그 등록 시 fomula 필드에 산술 수식을 넣으면, 드라이버가 PLC 에서 읽어온 원시 값(raw value)을 후처리하여 변환된 값으로 캐시 / Sparkplug / REST 응답에 사용합니다.

항목
구현 클래스plantpulse.app.edge.component.collector.plc.PLCValueFomula
호출 위치PLCValueReader.read() 의 3 단계 (raw → format → fomula → 캐시 갱신)
라이브러리plantpulse-edge-fomula.jar (plantpulse.edge.fomula.FormulaEngine)
변수 치환${VALUE} (현재 태그 값) / ${tag_id} (다른 태그의 마지막 값, LastValueMap 캐시)
적용 대상 data_typeFloat / Double / Integer / Long 만 적용. 그 외 타입(String, Boolean, …)은 그대로 통과
결과Formula.evaluate(...)BigDecimal → 타입별 렌더(정수 계열은 정수, Float/Double 은 소수)

동작 흐름

  1. 드라이버에서 raw 문자열 값을 받음 (예: "16384").

  2. PLCValueFomula.fomulaValue(address, value) 호출.

  3. address.getFomula() 가 비어있으면 그대로 반환.

  4. data_type 이 Float / Double / Integer / Long 이 아니면 그대로 반환.

  5. 계산식이 참조하는 이름을 확인 — VALUE 는 방금 읽은 값, 나머지는 LastValueMap 의 다른 태그 마지막 값.

  6. 참조 태그가 아직 한 번도 수집되지 않았으면 예외:

    Variable value referenced by the formula is not yet in cache : formula=[`<fomula>`], variable=[`<이름>`]
  7. 계산식이 참조하는 이름만 값으로 바인딩해 평가 → BigDecimal → 타입별 문자열 반환.


변수 종류

표기의미출처
${VALUE}현재 태그의 raw 값드라이버가 막 읽어온 값
${TAG_ID_X}다른 태그의 마지막 값LastValueMap (전체 태그 공용 캐시)

LastValueMap 은 모든 태그의 마지막 raw / 후처리 값을 보관합니다. 한 OPC 의 다른 태그뿐 아니라 다른 OPC 의 태그도 참조 가능합니다 (단, 그 태그가 한 번이라도 수집되어 캐시에 존재해야 함).

변수 미존재

${TAG_ZERO} 를 참조했는데 TAG_ZERO 가 아직 한 번도 수집되지 않았다면 값을 바인딩할 수 없어 예외가 발생합니다. 참조 태그의 timecycle 이 더 짧거나, 참조 태그를 먼저 수집되도록 auto_collect=true 로 등록 하세요. (저장 시점 문법 검사는 이 경우를 통과시킵니다 — 수집 순서 문제이지 수식 오류가 아닙니다.)


표현식 문법

엑셀과 같은 방식으로 씁니다. 함수 이름과 의미가 엑셀과 같으므로, 엑셀에서 쓰던 식을 거의 그대로 옮길 수 있습니다. 대소문자는 가리지 않습니다 (IF = if).

카테고리토큰비고
산술+, -, *, /, ^^ 는 거듭제곱
비교>, <, >=, <=, =, <>= 는 같음, <> 는 다름 (엑셀과 동일)
그룹(, )괄호 우선
상수PI, E원주율, 자연상수
조건IF, SWITCH, AND, OR, NOTIF(조건, 참일때, 거짓일때)
반올림ROUND, ROUNDUP, ROUNDDOWN, CEILING, FLOOR, INT, TRUNCROUND(값, 자리수)
수치ABS, SIGN, MOD, POWER, SQRT, CBRT, EXP, FACTMOD 의 나머지 부호는 제수를 따름
집계MIN, MAX, SUM, AVERAGE, COALESCE인자 여러 개
로그LOG, LOG10, LNLOG = 상용로그(밑10), LN = 자연로그
삼각SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2라디안 기준 (엑셀과 동일)
쌍곡SINH, COSH, TANH, ASINH, ACOSH, ATANH
각도 변환DEGREES, RADIANS라디안 ↔ 도
지원하지 않는 것
  • 비트 연산 (&, |)
  • 사용자 정의 함수
  • 문자열 연산 / 날짜 연산 이런 게 필요하면 후속 단계 (외부 함수 / 후처리 노드) 에서 다루세요.
잘못된 계산식은 저장되지 않습니다

문법이 맞지 않는 계산식은 저장 시점에 거부됩니다 (UI 저장 / CSV 업로드 / 백업 복원 모두). 태그 설정 모달에서 샘플 값을 넣어 결과를 미리 확인할 수 있습니다.

계산 중 실패(0 으로 나누기, 참조 값이 숫자가 아님 등)는 수집 오류로 기록되고 해당 태그의 품질이 떨어집니다. 그럴듯한 숫자를 대신 저장하지 않습니다.

변수 표기

${VALUE} 와 맨이름 VALUE 둘 다 씁니다. 다른 태그도 마찬가지입니다 — ${TAG_ZERO} = TAG_ZERO. 기존에 저장된 ${...} 계산식은 그대로 동작합니다.

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

풍부한 예제 모음

1. 단순 스케일 (×0.1)

항목
data_typeFloat
fomula${VALUE}*0.1
입력 → 출력163841638.4

용도: 정수 raw → 소수점 1자리 실수. 온도 / 압력 / 유량 센서가 정수로 값을 보낼 때.

2. 단위 변환 (mV → V)

항목
data_typeFloat
fomula${VALUE}/1000
입력 → 출력33003.3

3. 오프셋 보정 (섭씨 → 켈빈)

항목
data_typeFloat
fomula${VALUE}+273.15
입력 → 출력25298.15

4. 다른 태그 참조 (영점 보정)

항목
data_typeFloat
fomula${VALUE}-${TAG_ZERO}
입력 → 출력VALUE=1024, TAG_ZERO=241000

TAG_ZERO 는 다른 태그의 ID. 영점 / Tare 보정에 사용.

5. 다항식 (제곱)

항목
data_typeFloat
fomula${VALUE}*${VALUE}*0.001
입력 → 출력10010.0

6. 함수 — 제곱근

항목
data_typeFloat
fomulasqrt(${VALUE})
입력 → 출력14412.0

7. 함수 — 삼각 (sin, radian)

항목
data_typeFloat
fomulasin(${VALUE})
입력 → 출력1.5708 (≈π/2) → 1.0

각도(degree) 입력이라면 sin(${VALUE}*pi/180).

8. 다중 태그 — 캘리브레이션 (gain × x + offset)

항목
data_typeFloat
fomula${VALUE}*${TAG_GAIN}+${TAG_OFFSET}
입력 → 출력VALUE=100, GAIN=0.05, OFFSET=27.0

장비별 보정 계수를 별도 태그(또는 HTTP-bind 태그)로 관리하는 패턴.

9. 형변환 / 강제 실수화

항목
data_typeFloat
fomula${VALUE}*1.0
입력 → 출력123123.0

Integer 로 들어온 값을 Float 로 캐스팅만 하고 싶을 때.

10. 거듭제곱 / 지수

항목
data_typeFloat
fomula${VALUE}^2
입력 → 출력525.0

11. 로그

항목
data_typeFloat
fomulalog(${VALUE})
입력 → 출력1002.0

ln(...) 도 사용 가능 (자연로그).

12. 복합 — RMS 의 dB 변환

항목
data_typeFloat
fomula20*log(${VALUE})
입력 → 출력100060.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 를 넣어주면 됩니다.

값 read (후처리된 값이 반환됨):

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괄호 짝이 안 맞거나 연산자 뒤 피연산자 누락저장 시점에 거부되므로 모달의 오류 메시지가 위치까지 알려줍니다
후처리가 안 적용됨 (raw 그대로 반환)data_type 이 String/BooleanFloat/Double/Integer/Long 으로 변경하거나 후처리 필요시 별도 가공 단계 사용
결과가 항상 0raw 값이 실제로 0raw read 값을 먼저 GET /api/v1/tag/.../value 로 확인. (옛 파서는 ${VALUE}* 같은 불완전 수식을 조용히 0 으로 계산했지만, 지금은 그런 수식이 저장 단계에서 거부됩니다.)
수집 오류 FORMAT_FAILED + 품질 저하0 으로 나누거나 sqrt 음수 등 계산 실패Infinity/NaN 을 저장하지 않고 오류로 드러냅니다. 수식에 가드를 넣으세요 — 예: IF(TAG_ZERO=0, 0, VALUE/TAG_ZERO)

동작 메모

  • LastValueMap싱글톤 (in-memory). 게이트웨이 재시작 시 초기화 → 첫 사이클은 다른 태그 참조가 실패할 수 있음.
  • 계산은 전 구간 BigDecimal 이라 Long/QWord 의 큰 정수도 마지막 자리까지 보존됩니다(옛 double 평가에서 나던 손실 없음).
  • 결과는 Double.toString() → 문자열 캐시. 화면 / Sparkplug 발행 시 다시 data_type 으로 캐스팅됩니다 (Sparkplug DataTypeMapper 참고).
  • 같은 OPC 안에서 태그 여러 개의 fomula 가 서로를 참조하면, 수집 순서 에 따라 첫 사이클이 부분적으로 실패할 수 있습니다. 운영상 큰 문제는 아니지만(다음 사이클부터 정상), 깨끗하게 가려면 참조 대상 태그를 더 짧은 timecycle 의 별도 OPC 에 두세요.