헤드리스 시뮬레이터
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 패키지에는 모든 작업 공간을 도는 시나리오가 들어 있습니다.
[ {"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이어야 합니다.