OPC-UA 드라이버
개요
OPC Unified Architecture (IEC 62541) 기반 산업 데이터 표준 프로토콜.
| 항목 | 값 |
|---|---|
opc_type | OPCUA |
| 구현 클래스 | plantpulse.driver.protocol.opcua.OPCUA_34_Driver |
| 라이브러리 | Eclipse Milo (milo-sdk-client) |
| read | ✅ |
| write | ❌ (Phase 3 / Sparkplug NCMD 와 함께) |
| 보안 | Username/Password, Anonymous |
Kepware, Matrikon, Prosys 등 대부분의 OPC-UA 서버와 호환됩니다.
OPC 등록 폼
| 필드 | 의미 | 예시 |
|---|---|---|
opc_agent_ip | OPC-UA 서버 IP | 192.168.0.40 |
opc_agent_port | OPC-UA 서버 TCP 포트 | 49320 (Kepware 기본) |
timecycle | 폴링 주기 (ms) | 1000 |
options.servername | endpoint 끝의 서버명 | (Kepware 면 비움) |
options.username | 인증 사용자 | kopens |
options.password | 인증 패스워드 | *** |
options.discovery | discovery endpoint 사용 여부 | false (true 면 endpoints 자동 검색) |
생성되는 endpoint URL 형식:
opc.tcp://<opc_agent_ip>:<opc_agent_port>/<servername>
username + password 가 모두 있으면 UsernameProvider, 없으면 AnonymousProvider 사용.
태그 plc_address 형식
OPC-UA 표준 NodeId 표기. 예:
| 표기 | 의미 |
|---|---|
ns=2;s=Simulator.Sine1 | namespace 2, string identifier Simulator.Sine1 |
ns=2;i=1234 | numeric identifier 1234 |
ns=3;g=09087e75-8e5e-499b-954f-f2a8624db28a | GUID identifier |
ns=2;b=Mb= | opaque (bytestring) identifier |
Kepware 의 경우 보통 ns=2;s=<Channel>.<Device>.<Tag> 형식.
data_type / format 매핑
OPC-UA 변수 노드의 Value 를 String 으로 캐스팅 후 반환합니다. data_type 은 후속 단계
(PLCValueFomula, Sparkplug 발행 등) 가 캐스팅에 사용합니다.
data_type | 동작 |
|---|---|
Float / Double | 숫자 그대로 |
Int / Integer / Int32 / Short / Long | 숫자 그대로 |
Boolean / Bool | true/false 문자열 |
String / Text | UTF-8 문자열 |
DateTime | ISO 문자열 |
format 은 OPC-UA 에서는 일반적으로 사용하지 않습니다. 후처리 수식이 필요하면 fomula 사용.
흔한 에러 + 해결
| 메시지 / 증상 | 원인 | 해결 |
|---|---|---|
connection refused | 서버 미가동, 포트 차단 | telnet <ip> <port> 로 통신 확인 |
Bad_UserAccessDenied | username/password 오류, 익명 비허용 | 서버측 사용자 등록 또는 anonymous 활성화 |
Bad_SecurityChecksFailed | 보안 정책 미스매치 | 서버에서 None / Basic256Sha256 등 정책 확인. Phase 1 은 MessageSecurityMode.None 우선 |
endpointUrl 미스매치 | discovery=false 인데 서버가 다른 호스트네임 반환 | discovery=true 로 두거나 hosts 파일에 매핑 |
| 값이 항상 0 / null | NodeId 오타, namespace 번호 불일치 | UA Expert 등 클라이언트로 같은 NodeId read 검증 |
curl 등록 예시
curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_UA_Kepware",
"opc_type": "OPCUA",
"opc_name": "Kepware",
"opc_agent_ip": "192.168.0.40",
"opc_agent_port": "49320",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"options": {
"username": "kopens",
"password": "***",
"discovery": "false"
},
"tag_list": [
{
"tag_id": "OPC_UA_Kepware_TAG_00001",
"tag_name": "Sine1",
"plc_address": "ns=2;s=Simulator.Sine1",
"data_type": "Float",
"description": "Sine wave"
},
{
"tag_id": "OPC_UA_Kepware_TAG_00002",
"tag_name": "Random1",
"plc_address": "ns=2;s=Simulator.Random1",
"data_type": "Integer"
}
]
}'
값 read:
curl -s http://<edge-host>/api/v1/tag/OPC_UA_Kepware_TAG_00001/value | jq
참고: Kepware 빠른 설정 가이드
- Kepware Server → Project → 채널 추가 (예:
Simulator). - 디바이스 추가 (예:
Simulator) → 모델 =16 Bit Device. - 태그 추가 →
Sine1, typeFloat, scan rate100ms등. Project Properties → OPC UA → Server Endpoint에서 포트49320활성화.- Anonymous 또는 사용자 계정 활성화.
→ Edge 등록 폼:
| 필드 | 값 |
|---|---|
| opc_agent_ip | Kepware 서버 IP |
| opc_agent_port | 49320 |
| options.servername | (비움) |
| options.username/password | 위에서 만든 계정 |
| plc_address | ns=2;s=Simulator.Simulator.Sine1 |
예제 모음 (데이터타입별)
OPC-UA 는 변수 노드의 값(Variant)을 String 으로 캐스팅 후 반환합니다. data_type 은 후처리 (PLCValueFomula, Sparkplug DataTypeMapper) 가 캐스팅에 사용. format 은 일반적으로 사용하지 않습니다.
Boolean
data_type | plc_address 예 | 비고 |
|---|---|---|
Boolean | ns=2;s=Simulator.Boolean1 | Kepware Simulator 의 Boolean tag |
Boolean | ns=2;s=PLC.Status.Running | Kepware Channel.Device.Tag |
Boolean | ns=2;i=1003 | numeric NodeId |
16/32-bit 정수
data_type | plc_address 예 | 비고 |
|---|---|---|
Integer | ns=2;s=Simulator.Random1 | Random integer 시뮬 |
Integer | ns=2;s=Simulator.Counter1 | Counter |
Integer | ns=2;s=PLC.D100 | Kepware MELSEC 채널 노출 |
64-bit 정수
data_type | plc_address 예 | 비고 |
|---|---|---|
Long | ns=2;s=Simulator.LongCounter | UInt64 / Int64 노드 |
Long | ns=2;s=PLC.Cycle.Total | 누적 카운터 |
실수
data_type | plc_address 예 | 비고 |
|---|---|---|
Float | ns=2;s=Simulator.Sine1 | sine wave 시뮬 |
Float | ns=2;s=Simulator.Ramp1 | ramp wave |
Double | ns=2;s=PLC.Pressure | 정밀 측정값 |
문자열
data_type | plc_address 예 | 비고 |
|---|---|---|
String | ns=2;s=Simulator.User1.Name | 문자열 노드 |
String | ns=2;s=PLC.Recipe.Active | 레시피명 |
NodeId 형식별
| identifier 타입 | plc_address 예 |
|---|---|
String (s=) | ns=2;s=Simulator.Sine1 |
Numeric (i=) | ns=2;i=1234 |
GUID (g=) | ns=3;g=09087e75-8e5e-499b-954f-f2a8624db28a |
Opaque (b=) | ns=2;b=Mb= |
Formula 활용
| 용도 | data_type | fomula | 비고 |
|---|---|---|---|
| 단위 변환 | Float | ${VALUE}*0.001 | g → kg 등 |
| 영점 보정 | Float | ${VALUE}-${TAG_ZERO} | 다른 OPCUA 태그 참조 가능 |
| 형변환 | Float | ${VALUE}*1.0 | Integer → Float 강제 |
| 다항식 | Float | ${VALUE}*${VALUE}*0.0001 | 비선형 |
curl 종합 예제 (모든 타입)
curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_UA_FULL",
"opc_type": "OPCUA",
"opc_name": "Kepware Full",
"opc_agent_ip": "192.168.0.40",
"opc_agent_port": "49320",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"options": { "discovery": "false" },
"tag_list": [
{"tag_id":"OPC_UA_FULL_T01", "tag_name":"BoolTag", "plc_address":"ns=2;s=Simulator.Boolean1", "data_type":"Boolean"},
{"tag_id":"OPC_UA_FULL_T02", "tag_name":"IntTag", "plc_address":"ns=2;s=Simulator.Random1", "data_type":"Integer"},
{"tag_id":"OPC_UA_FULL_T03", "tag_name":"LongTag", "plc_address":"ns=2;s=Simulator.LongCounter", "data_type":"Long"},
{"tag_id":"OPC_UA_FULL_T04", "tag_name":"FloatTag", "plc_address":"ns=2;s=Simulator.Sine1", "data_type":"Float"},
{"tag_id":"OPC_UA_FULL_T05", "tag_name":"DoubleTag", "plc_address":"ns=2;s=PLC.Pressure", "data_type":"Double"},
{"tag_id":"OPC_UA_FULL_T06", "tag_name":"StringTag", "plc_address":"ns=2;s=Simulator.User1.Name", "data_type":"String"},
{"tag_id":"OPC_UA_FULL_T07", "tag_name":"NumIdTag", "plc_address":"ns=2;i=1234", "data_type":"Integer"},
{"tag_id":"OPC_UA_FULL_T08", "tag_name":"ScaledTag", "plc_address":"ns=2;s=Simulator.Sine1", "data_type":"Float", "fomula":"${VALUE}*100"}
]
}'