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

headless 시뮬레이터

pdcomm-simulator-headless는 화면 없이 실행하는 SECS/GEM 시뮬레이터입니다. 정해진 시나리오를 실행하고 결과를 한 줄에 JSON 객체 하나씩(JSON Lines) 표준 출력에 씁니다. 종료 코드로 성공과 실패를 알려 주므로 자동 시험이나 CI 스크립트에 넣기 좋습니다.

headless 시뮬레이터로 다음을 할 수 있습니다.

  • 내부에서 호스트와 장비 한 쌍을 만들어 기능별 왕복을 확인합니다.
  • 시뮬레이터 장비나 호스트 하나만 띄워 자신의 프로그램과 연결합니다.
  • GUI에서 저장한 프로젝트의 프로필과 시나리오를 실행하고 추적(trace) 파일을 남깁니다.
  • 개발 환경 준비의 환경 변수(PDCOMM_SIMULATOR)를 정합니다.
  • 실행 파일은 Windows에서 $Simulator\pdcomm-simulator-headless.exe, Linux에서 "$PDCOMM_SIMULATOR/pdcomm-simulator-headless"입니다. Linux의 이 파일은 라이브러리 경로를 정한 뒤 bin/의 실제 프로그램을 실행하는 스크립트입니다.

이 페이지의 명령은 Linux와 macOS 셸 기준입니다. Windows에서는 & "$Simulator\pdcomm-simulator-headless.exe" ...로 실행하고 $LASTEXITCODE로 종료 코드를 확인합니다.

터미널 창
"$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --scenario status-round-trip; echo "exit=$?"
{"schema":1,"sequence":1,"scenario":"status-round-trip","scope":"pair","kind":"start","detail":"scenario-started","truncated":false}
{"schema":1,"sequence":2,"scenario":"status-round-trip","scope":"equipment","kind":"endpoint","local_address":"127.0.0.1:49900","truncated":false}
...
{"schema":1,"sequence":7,"scenario":"status-round-trip","scope":"host","kind":"result","detail":"status-name","id":1001,"id_present":true,"value_present":false,"format":0,"preview":"","name":"EquipmentState",...}
...
{"schema":1,"sequence":10,"scenario":"status-round-trip","scope":"host","kind":"result","detail":"status-value","id":1001,"id_present":true,"value_present":true,"format":16,"preview":"IDLE",...}
...
{"schema":1,"sequence":15,"scenario":"status-round-trip","scope":"pair","kind":"summary","outcome":"passed","exit_code":0,"elapsed_ms":26,"records_emitted":15,"max_records":128,"truncated":false,...}
exit=0

--scenario를 생략하면 smoke를 실행합니다. 주소의 포트 번호와 elapsed_ms는 실행할 때마다 다릅니다.

범위 --scenario 값 확인하는 내용
기본 smoke 연결, Select, 통신 수립
status-round-trip 상태 변수 이름(S1F11)과 값(S1F3)
data-report-round-trip 데이터 변수와 이벤트 보고
directory-round-trip 데이터·이벤트 디렉터리(S1F21, S1F23)
조회·설정·명령 report-query-round-trip 보고서 조회
remote-report-config-round-trip 호스트의 보고서 정의·연결·사용 설정(S2F33, S2F35, S2F37)
typed-command-round-trip 원격 명령
저장·상수 project-round-trip 프로젝트 문서 저장과 복원
constants-snapshot-parity 장비 상수 스냅샷
가상·선언형 secs1-virtual-pair SECS-I 가상 쌍
gem300-capability-profile-lifecycle GEM300 기능 카탈로그
정형 공정 프로그램 verification-report-round-trip, formatted-program-transfer-round-trip, formatted-program-store-reverse-round-trip, formatted-program-query-round-trip 검증 보고, 전송, 저장, 조회
formatted-program-selection-lifecycle, formatted-program-dry-run, formatted-program-execution-lifecycle 선택, 구조 분석, 실행
formatted-program-verification-request-round-trip, formatted-program-capability-round-trip 검증 요청, 기능 조회
formatted-program-verification-negotiation-round-trip, formatted-program-verification-negotiation-reverse-round-trip, formatted-program-verification-negotiation-denied 검증 협상
전송 장애 transport-backpressure-recovery 쓰기 배압(backpressure)과 회복. --transport-fault-profile write_backpressure가 필요합니다.
외부 프로그램 external-peer 외부 호스트의 S1F13과 S1F1을 기다립니다. 아래 절을 참고합니다.

...-denied 시나리오의 성공(exit_code 0)은 거부가 의도대로 일어나고 상태가 보존되었다는 뜻입니다. 각 시나리오의 내부 장비 카탈로그는 시나리오마다 정해져 있으며, GUI의 기본 카탈로그와 같다고 가정하지 않습니다.

모든 줄에 다음 필드가 있습니다.

필드 설명
schema 출력 형식 버전. 현재 1입니다.
sequence 1부터 증가하는 줄 번호
scenario 실행한 시나리오 이름. 프로젝트 모드에서는 시나리오 ID, 인자 오류에서는 invalid입니다.
scope 기록의 주체: pair, host, equipment, secs1, project, transport, trace, cli 등
kind start, endpoint, request, result, error, summary 등
detail 단계 이름(예: begin-communication, status-value)
truncated 이 줄의 값이 잘렸는지 여부

마지막 줄은 항상 "kind":"summary"입니다.

필드 설명
outcome passed, failed, timeout, interrupted, invalid-arguments(인자 오류), invalid-input(입력 파일 오류)
exit_code 프로세스 종료 코드와 같습니다.
elapsed_ms 실행 시간(밀리초)
records_emitted, max_records 출력한 줄 수와 상한(128). 상한에 닿으면 이후 기록은 버리고 truncated가 true가 됩니다.
코드 의미
0 성공
2 인자 또는 입력 파일 오류
3 시나리오 실패(프로토콜 오류, 실패한 요청 등)
4 시간 초과(--timeout-ms)
130 Ctrl+C 또는 SIGTERM으로 중단

알 수 없는 시나리오를 주면 다음처럼 끝납니다.

터미널 창
"$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --scenario nope; echo "exit=$?"
{"schema":1,"sequence":1,"scenario":"invalid","scope":"cli","kind":"error","detail":"scenario is unsupported","truncated":false}
{"schema":1,"sequence":2,"scenario":"invalid","scope":"cli","kind":"summary","outcome":"invalid-arguments","exit_code":2,...}
exit=2
옵션 설명
--scenario <이름> 실행할 시나리오. 기본값은 smoke입니다. 프로젝트 모드에서는 프로젝트의 시나리오 ID입니다.
--session-id <0–65535> HSMS 세션 ID. 기본값은 0입니다. 외부 프로그램과 연결할 때 상대와 맞춥니다.
--timeout-ms <100–60000> 시나리오 제한 시간. 기본값은 8000입니다.
--role host|equipment 와 --address <IPv4:port> 한쪽 역할만 실행합니다. 두 옵션을 함께 주며 smoke 시나리오에만 쓸 수 있습니다.
--project <파일> GUI에서 저장한 프로젝트 파일. --scenario에 프로젝트의 시나리오 ID를 줍니다.
--trace-output <파일> 프로젝트 모드에서 실행한 세션의 추적을 파일로 저장합니다.
--trace-input <파일> 저장한 추적 파일의 형식만 검사합니다. 다른 옵션과 함께 쓸 수 없습니다.
--transport-fault-profile write_backpressure transport-backpressure-recovery 시나리오에서 쓰기 배압을 일으킵니다.

--help 옵션은 없습니다. 알 수 없는 옵션을 주면 종료 코드 2로 끝납니다.

내 호스트 프로그램을 시험합니다

섹션 제목: “내 호스트 프로그램을 시험합니다”

external-peer 시나리오는 시뮬레이터 장비를 127.0.0.1의 빈 포트에 띄우고, 외부 호스트가 S1F13(통신 수립)과 S1F1(Are You There)을 보낸 뒤 연결을 끊을 때까지 기다립니다. 자신의 호스트 프로그램을 자동으로 시험할 때 씁니다.

터미널 창
"$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --scenario external-peer --session-id 7 --timeout-ms 20000

두 번째 줄에 장비 주소가 나옵니다.

{"schema":1,"sequence":2,"scenario":"external-peer","scope":"equipment","kind":"endpoint","local_address":"127.0.0.1:50092","truncated":false}

이 주소로 호스트를 연결합니다. 여기서는 튜토리얼 C++ 호스트로 establish, identity, quit을 차례로 입력합니다.

터미널 창
"$PDCOMM_ROOT/build/pdcomm-tutorial" host active 127.0.0.1:50092

호스트가 끊으면 시뮬레이터가 관찰한 내용과 요약을 출력하고 종료 코드 0으로 끝납니다.

{"schema":1,"sequence":3,"scenario":"external-peer","scope":"equipment","kind":"result","detail":"s1f13","truncated":false}
{"schema":1,"sequence":4,"scenario":"external-peer","scope":"equipment","kind":"result","detail":"s1f13-system-bytes=101","truncated":false}
{"schema":1,"sequence":5,"scenario":"external-peer","scope":"equipment","kind":"result","detail":"s1f1","truncated":false}
{"schema":1,"sequence":6,"scenario":"external-peer","scope":"equipment","kind":"result","detail":"s1f1-system-bytes=102","truncated":false}
{"schema":1,"sequence":7,"scenario":"external-peer","scope":"equipment","kind":"result","detail":"disconnect","truncated":false}
{"schema":1,"sequence":8,"scenario":"external-peer","scope":"pair","kind":"summary","outcome":"passed","exit_code":0,"elapsed_ms":2942,...}
  • 시스템 바이트(system bytes)는 호스트가 정한 값입니다. 튜토리얼 호스트는 100부터 시작하므로 101, 102가 나옵니다.
  • 제한 시간 안에 두 메시지를 받지 못하면 "outcome":"timeout", 종료 코드 4로 끝납니다. 기본 제한 시간은 8초이므로 손으로 입력할 때는 --timeout-ms를 늘립니다.
  • 세션 ID가 다르면 데이터 메시지를 처리하지 못합니다. 튜토리얼 예제는 세션 ID 7을 씁니다.

--role과 --address를 주면 시뮬레이터가 장비나 호스트 하나만 실행합니다.

장비로 실행하면 지정한 주소에서 기다립니다. 호스트가 통신을 수립한 뒤 연결을 끊으면 성공으로 끝납니다. 이 장비는 상태 변수 1001(EquipmentState, ASCII IDLE)을 등록합니다.

터미널 창
"$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --role equipment --address 127.0.0.1:5001 --session-id 7 --timeout-ms 5000

호스트로 실행하면 지정한 주소의 장비에 연결해 통신을 수립하고 끝납니다. 튜토리얼 장비(equipment passive 127.0.0.1:5006)를 먼저 실행한 뒤 다음을 실행합니다.

터미널 창
"$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --role host --address 127.0.0.1:5006 --session-id 7
{"schema":1,"sequence":1,"scenario":"smoke","scope":"host","kind":"start","detail":"scenario-started","truncated":false}
{"schema":1,"sequence":2,"scenario":"smoke","scope":"host","kind":"endpoint","local_address":"127.0.0.1:55752","truncated":false}
{"schema":1,"sequence":3,"scenario":"smoke","scope":"host","kind":"request","detail":"begin-communication","truncated":false}
{"schema":1,"sequence":4,"scenario":"smoke","scope":"host","kind":"summary","outcome":"passed","exit_code":0,...}

호스트 역할은 0이 아닌 포트가 필요합니다. 주소는 숫자 IPv4와 포트만 받습니다.

프로젝트의 시나리오를 실행합니다

섹션 제목: “프로젝트의 시나리오를 실행합니다”

GUI의 Scenarios & Profiles에서 만든 프로필(profile)과 시나리오(scenario)는 File > **Save Project As…**로 저장한 프로젝트 파일에 들어 있습니다. headless 시뮬레이터는 이 파일을 읽어 장비 세션을 시작하고, 프로필을 적용하고, 시나리오의 단계를 실행합니다.

다음은 프로필 PROFILE-DEMO와 시나리오 count-cycle이 들어 있는 최소 프로젝트 파일입니다. 시나리오는 데이터 변수 2001에 7을 넣었다가 지웁니다.

tutorial.pdcomm.json
{
"kind": "pdcomm-simulator-project",
"schema_version": 9,
"equipment_alarms": null,
"equipment_commands": null,
"equipment_constants": null,
"equipment_data_variables": null,
"equipment_directories": null,
"equipment_programs": null,
"equipment_reports_events": null,
"equipment_spooling": null,
"profiles": [
{
"profile_id": "PROFILE-DEMO",
"description": "Tutorial profile",
"variables": [
{"kind": "status", "id": 1001, "name": "State", "units": "", "format": "ascii",
"min_count": 0, "max_count": 32, "initial_value": "IDLE"},
{"kind": "data", "id": 2001, "name": "GoodCount", "units": "count", "format": "u4",
"min_count": 1, "max_count": 1, "initial_value": "0"}
]
}
],
"scenarios": [
{
"scenario_id": "count-cycle",
"description": "Set and clear GoodCount",
"profile_id": "PROFILE-DEMO",
"steps": [
{"kind": "set_data_variable", "variable_id": 2001, "value": "7"},
{"kind": "clear_data_variable", "variable_id": 2001, "value": ""}
]
}
]
}
터미널 창
"$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --project tutorial.pdcomm.json --scenario count-cycle --trace-output count-cycle.pdcomm-trace.json
{"schema":1,"sequence":2,"scenario":"count-cycle","scope":"project","kind":"result","detail":"project-file-loaded","truncated":false}
...
{"schema":1,"sequence":8,"scenario":"count-cycle","scope":"project","kind":"result","detail":"project-step","step_index":0,"variable_id":2001,"action":"set-data-variable","passed":true,"truncated":false}
{"schema":1,"sequence":9,"scenario":"count-cycle","scope":"project","kind":"result","detail":"project-step","step_index":1,"variable_id":2001,"action":"clear-data-variable","passed":true,"truncated":false}
{"schema":1,"sequence":10,"scenario":"count-cycle","scope":"project","kind":"result","detail":"project-terminal","state":"passed","completed_steps":2,"total_steps":2,"truncated":false}
{"schema":1,"sequence":11,"scenario":"count-cycle","scope":"project","kind":"summary","outcome":"passed","exit_code":0,...}

저장한 추적 파일은 GUI의 File > **Open Trace…**로 열어 볼 수 있습니다. 파일 형식만 확인하려면 --trace-input을 씁니다.

터미널 창
"$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --trace-input count-cycle.pdcomm-trace.json
{"schema":1,"sequence":1,"scenario":"trace-validation","scope":"trace","kind":"summary","outcome":"passed","exit_code":0,...}

transport-backpressure-recovery는 송신 버퍼가 가득 찬 상황을 일부러 만들고 회복을 확인합니다.

터미널 창
"$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --scenario transport-backpressure-recovery --transport-fault-profile write_backpressure

출력의 detail은 transport-fault-armed → first-send-accepted → second-send-limit-exceeded → transport-fault-released → transport-recovered → retry-send-accepted 순서로 진행하고 "outcome":"passed"로 끝납니다. 두 번째 송신은 한도 초과로 거부되고, 장애를 풀면 같은 요청을 다시 보낼 수 있습니다. 실제 프로그램에서도 Backpressure를 받으면 수신 이벤트를 먼저 처리한 뒤 정해진 횟수만 다시 시도합니다.

종료 코드만 확인하면 됩니다. 결과 파일은 실패 원인을 볼 수 있도록 남겨 둡니다.

터미널 창
set -e
mkdir -p results
for scenario in smoke status-round-trip data-report-round-trip directory-round-trip; do
"$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --scenario "$scenario" > "results/$scenario.jsonl"
done