콘텐츠로 이동
무료 평가 시작

헤드리스 시뮬레이터

pdcomm-opcua-headless는 화면 없이 동작하는 OPC UA 시뮬레이터입니다. GUI와 같은 엔진으로 서버, 클라이언트, PubSub을 실행합니다. 스크립트, 자동 시험, CI에서 씁니다. apps 패키지의 bin/에 있습니다.

Usage: pdcomm-opcua-headless [--advertised-host host] [--scenario path]

인자 없이 실행하면 대화형 모드, --scenario를 주면 시나리오 모드입니다. --advertised-host는 시작 구성의 server.advertised_host를 정합니다(구성 참고). 다른 인자를 주면 사용법을 출력하고, 쓸 수 없는 호스트 이름이면 Advertised host must be a host name or an IP address를 출력하고 종료 코드 2로 끝납니다.

표준 입력에서 JSON 객체를 한 줄에 하나씩 읽고, 표준 출력에 응답을 한 줄에 하나씩 씁니다.

{"action":"read","nodes":["ns=2;s=Simulator.Double"]}
  • 요청은 action과 동작별 인자를 가진 객체입니다.
  • 성공 응답은 {"ok":true,"result":...}, 실패 응답은 {"ok":false,"error":"..."}입니다. 오류 문장은 영어입니다.
  • JSON이 아니면 {"error":"expected ident at line 1 column 2","ok":false}처럼 해석 오류를 돌려줍니다. 알 수 없는 동작은 Unknown service action입니다.
  • 한 줄은 1 MiB 이하입니다. 넘으면 Input exceeds one MiB를 돌려주고 끝납니다.
  • 표준 출력에는 응답만 씁니다. 진단 메시지는 표준 오류나 설정한 로그 파일로 갑니다.
  • 명령을 기다리는 동안에도 시뮬레이터는 클라이언트 연결을 관리합니다.

특별한 요청이 두 가지 있습니다.

요청 동작
{"action":"cancel"} 실행 중인 동작을 취소합니다. 취소된 동작은 {"error":"Cancelled","ok":false}로 끝나고, 이 요청은 {"ok":true,"result":{"cancel_requested":true}}를 돌려줍니다. 실행 중인 동작이 없으면 cancel_requested가 false입니다.
{"action":"shutdown"} 클라이언트, PubSub, 서버를 차례로 정리하고 끝냅니다. 표준 입력이 끝나도(EOF) 같은 방식으로 끝납니다.

실패한 명령이 하나라도 있으면 종료 코드는 1, 없으면 0입니다.

시나리오 파일은 단계(step)의 JSON 배열입니다. apps 패키지에는 모든 작업 공간을 도는 시나리오가 들어 있습니다.

examples/opcua/scenarios/simulator-tour.json
[
{"command": {"action": "server_start"}, "expect": {"running": true}},
{"command": {"action": "connect"}, "expect": {"connected": true, "policy": "None"}},
{"command": {"action": "browse", "node": "ns=2;s=Simulator"}, "expect": {"good": true, "more": false}},
{"command": {"action": "write", "node": "ns=2;s=Simulator.Double", "type": "Double", "value": 2.5},
"expect": {"good": true}},
{"command": {"action": "write", "node": "ns=2;s=Simulator.Counter", "type": "UInt32", "value": 1},
"expect": {"good": false, "status": "BadNotWritable (0x803B0000)"}},
{"command": {"action": "read", "nodes": ["ns=2;s=Simulator.Double", "ns=2;s=Simulator.Protected"]}},
{"command": {"action": "call", "method": "ns=2;s=Simulator.Add",
"arguments": [{"type": "Double", "value": 1.5}, {"type": "Double", "value": 2}]},
"expect": {"good": true, "outputs": [{"type": "Double", "value": 3.5}]}},
{"command": {"action": "subscribe", "nodes": ["ns=2;s=Simulator.Double"], "interval_ms": 250},
"expect": {"subscription": 1}},
{"command": {"action": "publish", "count": 2}},
{"command": {"action": "event_subscribe"}, "expect": {"good": true}},
{"command": {"action": "alarm_set", "active": true}, "expect": {"good": true}},
{"command": {"action": "publish", "count": 2}},
{"command": {"action": "condition_refresh"}, "expect": {"good": true}},
{"command": {"action": "history_read", "node": "ns=2;s=Simulator.Double"}, "expect": {"good": true}},
{"command": {"action": "pubsub_start"}, "expect": {"running": true, "transport": "udp"}},
{"command": {"action": "pubsub_publish"}, "expect": {"sequence": 1}},
{"command": {"action": "pubsub_stop"}, "expect": {"running": false}},
{"command": {"action": "connect"}, "expect_error": true},
{"command": {"action": "disconnect"}, "expect": {"connected": false}},
{"command": {"action": "server_stop"}, "expect": {"running": false}}
]
필드 의미
command 실행할 요청. 대화형 모드의 한 줄과 같습니다.
expect 결과 객체에서 확인할 필드와 값. 적은 필드만 비교합니다.
expect_error true이면 이 단계가 실패해야 합니다.

실행합니다.

터미널 창
"$OPCUA_APPS/bin/pdcomm-opcua-headless" --scenario "$OPCUA_APPS/examples/opcua/scenarios/simulator-tour.json"

단계마다 {"result":...,"step":N} 한 줄을 출력합니다. 앞부분과 끝부분은 다음과 같습니다(긴 줄은 생략했습니다).

{"result":{"audit":[],"endpoint":"opc.tcp://localhost:63408/pdcomm/simulator","error":null,"running":true,"secure":true,"sessions":0},"step":1}
...
{"result":{"good":false,"node":"ns=2;s=Simulator.Counter","status":"BadNotWritable (0x803B0000)"},"step":5}
...
{"result":{"error":"The client is already connected"},"step":18}
{"result":{"connected":false},"step":19}
{"result":{"running":false},"step":20}

모든 단계가 기대와 맞으면 종료 코드는 0입니다. 맞지 않으면 그 단계에서 멈추고 표준 오류에 이유를 쓴 뒤 종료 코드 1로 끝납니다.

표준 오류 의미
Scenario step 2 field policy did not match expect의 필드 값이 결과와 다릅니다.
Scenario step 1 failed: The client is not connected 실패하지 않아야 할 단계가 실패했습니다.
Scenario step 1 succeeded unexpectedly expect_error인 단계가 성공했습니다.

시나리오 파일은 1 MiB, 10000단계를 넘을 수 없습니다. 시나리오 모드는 단계 사이에 기다리지 않습니다. 받은 PubSub 메시지처럼 시간이 걸리는 결과는 대화형 모드에서 확인합니다.

configure는 구성 객체를 받습니다. 일부 섹션이나 필드만 넘길 수 있습니다. 넘기지 않은 필드는 현재 값이 아니라 기본값이 됩니다. 일부만 바꾸려면 state로 현재 구성을 받아 고친 뒤 넘깁니다. 실행 중인 서버, 클라이언트, PubSub의 섹션은 멈춘 뒤에 바꿀 수 있습니다. 바꾸려 하면 Stop the endpoint before changing its configuration입니다. 현재 구성은 {"action":"state"}로 확인합니다.

{"action":"configure","config":{"server":{"address":"127.0.0.1:4841"},"client":{"policy":"Basic256Sha256","mode":"SignAndEncrypt"}}}

server

필드 기본값 설명
address 127.0.0.1:0 수신 소켓 주소. 포트 0은 빈 포트입니다.
path pdcomm/simulator endpoint URL 경로
advertised_host 빈 값 서버가 GetEndpoints, FindServers, CreateSession 응답의 URL에 넣는 호스트 이름(HostName). 비우면 각 클라이언트가 접속에 쓴 호스트와 포트를 그대로 돌려줍니다. 이름이나 IP 주소(IPv6는 대괄호 없이)를 넣으면 항상 그 이름을 돌려줍니다. 상태의 endpoint도 이 이름을 씁니다.
application_uri urn:pdcomm:opcua:server ApplicationUri. urn:으로 시작해야 합니다.
allow_none true None 엔드포인트 광고
secure true 서명·암호화 엔드포인트 광고. allow_none과 둘 다 거짓일 수 없습니다.
allow_anonymous true 익명 사용자 허용
user_name operator 받을 사용자 이름. 암호는 server_start에 줍니다.
certificate, private_key 빈 값 서버 인증서와 개인 키 파일(PEM 또는 DER). 비우면 메모리에서 자체 서명 인증서를 만듭니다. 이 인증서에는 localhost, 수신 IP 주소(0.0.0.0 제외), advertised_host가 들어갑니다.
trust [] 신뢰할 클라이언트 인증서 파일 목록(최대 64개)
simulation_interval_ms 500 Simulator.Counter 주기. 0 또는 50~60000
max_sessions 8 최대 세션 수(1~32)

client

필드 기본값 설명
url 빈 값 접속할 opc.tcp URL. 비우면 이 시뮬레이터의 서버입니다.
policy, mode None, None 보안 정책과 모드. None은 둘 다 None일 때만 씁니다.
identity anonymous anonymous 또는 username
user_name operator 사용자 이름. 암호는 connect에 줍니다.
application_uri urn:pdcomm:opcua:client ApplicationUri
certificate, private_key 빈 값 클라이언트 인증서와 개인 키 파일. 비우면 메모리에서 만듭니다.
trust [] 신뢰할 서버 인증서 파일 목록. 같은 시뮬레이터의 서버 인증서는 자동으로 신뢰합니다.
timeout_ms 5000 제한 시간(100~60000 ms)

pubsub

필드 기본값 설명
transport udp udp 또는 mqtt
role both both, publisher, subscriber
listen 127.0.0.1:0 UDP 구독자 수신 주소
destination 빈 값 UDP 게시자 목적지. 비우면 같은 프로세스의 구독자
broker 127.0.0.1:1883 MQTT 브로커 IP 주소와 포트
topic pdcomm/opcua/simulator MQTT 토픽. 와일드카드를 쓸 수 없습니다.
publisher_id, writer_group_id, dataset_writer_id 77, 1, 17 PubSub 식별자. 0이 아니어야 합니다.

limits: notification_queue(알림 보관 개수)와 trace_entries(동작 기록 개수), 각각 1~1000, 기본 1000.

값을 받는 인자(write, server_write, call의 arguments, pubsub_publish의 fields)는 타입과 값을 함께 씁니다.

{"type":"Double","value":12.5}
타입 값 형식
Boolean true, false
SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64 범위 안의 정수
Float, Double 숫자
String 문자열(64 KiB 이하)
LocalizedText 문자열
DateTime Unix 밀리초
ByteString 16진수 문자열
NodeId ns=2;s=Name 같은 NodeId 텍스트

NodeId 텍스트는 숫자(i=)와 문자열(s=) 식별자를 받습니다. 결과의 시각은 모두 Unix 밀리초입니다.

작업 공간 동작 인자
connection capabilities 없음. 지원 기능과 한도를 돌려줍니다.
connection state 없음. 구성과 서버·클라이언트·PubSub 상태
connection configure config
connection server_start password(선택, 256바이트 이하). 암호가 있으면 사용자 이름 토큰을 광고합니다.
connection server_stop, server_status 없음. server_status는 세션 수와 감사(audit) 기록을 돌려줍니다.
connection discover url(선택)
connection connect url(선택), password(identity가 username일 때)
connection disconnect 없음
connection project_export, project_import project_import는 project. 서버, 클라이언트, PubSub이 모두 멈춘 상태여야 합니다.
address_space browse node(기본 i=85), direction(forward 또는 inverse)
address_space server_nodes 없음. 서버 변수의 현재 값
read_write_methods read node 또는 nodes(최대 100), attribute(1~27, 기본 13)
read_write_methods write, server_write node, type, value
read_write_methods call object(기본 ns=2;s=Simulator), method, arguments(최대 32)
subscriptions subscribe node 또는 nodes(최대 100), interval_ms(50~10000, 기본 250)
subscriptions publish count(1~10, 기본 1)
subscriptions notifications, notifications_clear, unsubscribe 없음
events event_subscribe notifier(기본 i=2253), interval_ms
events alarm_set active
events condition condition(기본 ns=2;s=Simulator.Alarm), method(acknowledge, confirm, add_comment, enable, disable), event_id(16진수), comment(1024바이트 이하)
events condition_refresh 없음
history history_read node, start, end, max, continuation, release
pubsub pubsub_start, pubsub_status, pubsub_received, pubsub_stop 없음
pubsub pubsub_publish fields(1~64, 선택)
trace trace, trace_clear 없음
trace logging_configure config
trace logging_status, logging_flush 없음

암호는 동작마다 따로 넘기고 구성, 프로젝트, 응답, 로그 어디에도 남지 않습니다.

project_export는 현재 구성과 서버의 쓰기 가능한 공개 변수 값을 돌려줍니다. GUI의 Project 메뉴에서 같은 파일을 열고 저장합니다.

{"action":"project_export"}
{"ok":true,"result":{"config":{"client":{"application_uri":"urn:pdcomm:opcua:client","certificate":"","identity":"anonymous","mode":"None","policy":"None","private_key":"","timeout_ms":5000,"trust":[],"url":"","user_name":"operator"},"limits":{"notification_queue":1000,"trace_entries":1000},"pubsub":{"broker":"127.0.0.1:1883","dataset_writer_id":17,"destination":"","listen":"127.0.0.1:0","publisher_id":77,"role":"both","topic":"pdcomm/opcua/simulator","transport":"udp","writer_group_id":1},"server":{"address":"127.0.0.1:0","advertised_host":"","allow_anonymous":true,"allow_none":true,"application_uri":"urn:pdcomm:opcua:server","certificate":"","max_sessions":8,"path":"pdcomm/simulator","private_key":"","secure":true,"simulation_interval_ms":500,"trust":[],"user_name":"operator"}},"kind":"pdcomm.opcua.project","model":[],"version":1}}

result 객체를 파일로 저장한 뒤 {"action":"project_import","project":{...}}로 불러옵니다. kind는 pdcomm.opcua.project, version은 1이어야 합니다.