Skip to content
Start free evaluation

Headless simulator

pdcomm-simulator-headless is the SECS/GEM simulator without a window. It runs one scenario, writes one JSON object per line (JSON Lines) to standard output, and reports success or failure in its exit code, which makes it a good fit for automated tests and CI scripts.

Use it to:

  • Run an internal host and equipment pair through one capability round trip.
  • Start a single simulated equipment or host and connect your own program to it.
  • Replay the profiles and scenarios of a project you saved in the GUI, and keep a trace file.
  • Set the PDCOMM_SIMULATOR variable from Set up your environment.
  • The executable is $Simulator\pdcomm-simulator-headless.exe on Windows and "$PDCOMM_SIMULATOR/pdcomm-simulator-headless" on Linux. On Linux, that file is a launcher script that sets the library paths and then runs the real program in bin/.

The commands on this page use a Linux or macOS shell. On Windows, run & "$Simulator\pdcomm-simulator-headless.exe" ... and read the exit code from $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

Without --scenario, the simulator runs smoke. The port in the address and elapsed_ms change on every run.

Area --scenario value What it checks
Basics smoke Connection, Select and communication establishment
status-round-trip Status variable names (S1F11) and values (S1F3)
data-report-round-trip Data variables and event reports
directory-round-trip Data and event directories (S1F21, S1F23)
Queries, configuration, commands report-query-round-trip Report queries
remote-report-config-round-trip Host-side report definition, linking and enabling (S2F33, S2F35, S2F37)
typed-command-round-trip Remote commands
Storage and constants project-round-trip Saving and restoring a project document
constants-snapshot-parity Equipment constant snapshots
Virtual and declarative secs1-virtual-pair The SECS-I virtual pair
gem300-capability-profile-lifecycle The GEM300 capability catalog
Formatted process programs verification-report-round-trip, formatted-program-transfer-round-trip, formatted-program-store-reverse-round-trip, formatted-program-query-round-trip Verification reports, transfer, storage and queries
formatted-program-selection-lifecycle, formatted-program-dry-run, formatted-program-execution-lifecycle Selection, structural analysis and execution
formatted-program-verification-request-round-trip, formatted-program-capability-round-trip Verification requests and capability queries
formatted-program-verification-negotiation-round-trip, formatted-program-verification-negotiation-reverse-round-trip, formatted-program-verification-negotiation-denied Verification negotiation
Transport faults transport-backpressure-recovery Write backpressure and recovery. Requires --transport-fault-profile write_backpressure.
External programs external-peer Waits for S1F13 and S1F1 from an external host. See below.

When the ...-denied scenario passes (exit_code 0), the rejection happened as intended and the state was preserved. Each scenario defines its own internal equipment catalog; don’t assume it matches the GUI’s default catalog.

Every line has these fields:

Field Description
schema Output format version, currently 1
sequence Line number, starting at 1
scenario The scenario you ran: the scenario ID in project mode, or invalid for argument errors
scope Who produced the record: pair, host, equipment, secs1, project, transport, trace, cli and so on
kind start, endpoint, request, result, error, summary and so on
detail The step name, for example begin-communication or status-value
truncated Whether a value on this line was shortened

The last line always has "kind":"summary":

Field Description
outcome passed, failed, timeout, interrupted, invalid-arguments (bad arguments) or invalid-input (bad input file)
exit_code The same value as the process exit code
elapsed_ms Run time in milliseconds
records_emitted, max_records Lines written and the limit (128). At the limit, later records are dropped and truncated becomes true.
Code Meaning
0 Passed
2 Bad arguments or a bad input file
3 The scenario failed (protocol error, failed request and so on)
4 Timed out (--timeout-ms)
130 Interrupted with Ctrl+C or SIGTERM

An unknown scenario ends like this:

터미널 창
"$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
Option Description
--scenario <name> The scenario to run. Defaults to smoke. In project mode, a scenario ID from the project.
--session-id <0-65535> The HSMS session ID. Defaults to 0. Match it to your peer when you connect an external program.
--timeout-ms <100-60000> The scenario time limit. Defaults to 8000.
--role host|equipment with --address <IPv4:port> Run a single role. Pass both options together; works with the smoke scenario only.
--project <file> A project file saved from the GUI. Pass one of its scenario IDs in --scenario.
--trace-output <file> In project mode, save the session trace to a file.
--trace-input <file> Only validate a saved trace file. Can’t be combined with other options.
--transport-fault-profile write_backpressure Inject write backpressure in the transport-backpressure-recovery scenario.

There’s no --help option; any unknown option ends with exit code 2.

The external-peer scenario starts a simulated equipment on a free port of 127.0.0.1 and waits until an external host sends S1F13 (establish communication) and S1F1 (Are You There) and then disconnects. Use it to test your host program automatically.

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

The second line shows the equipment address:

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

Connect your host to that address. This example uses the tutorial C++ host; type establish, identity and quit:

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

When the host disconnects, the simulator reports what it saw and exits with code 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,...}
  • Your host chooses the system bytes. The tutorial host starts at 100, so you see 101 and 102.
  • If both messages don’t arrive in time, the run ends with "outcome":"timeout" and exit code 4. The default limit is eight seconds, so raise --timeout-ms when you type commands by hand.
  • With different session IDs, the equipment can’t process data messages. The tutorial examples use session ID 7.

With --role and --address, the simulator runs only an equipment or only a host.

As an equipment, it waits on the address you give. It passes when a host establishes communication and then disconnects. This equipment registers status variable 1001 (EquipmentState, ASCII IDLE).

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

As a host, it connects to the equipment at the address, establishes communication and exits. Start the tutorial equipment (equipment passive 127.0.0.1:5006) first, then run:

터미널 창
"$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,...}

The host role needs a nonzero port. Addresses must be a numeric IPv4 address with a port.

Profiles and scenarios that you build in the GUI’s Scenarios & Profiles workspace are saved in the project file you write with File > Save Project As…. The headless simulator reads that file, starts an equipment session, applies the profile and runs the scenario steps.

Here’s a minimal project with the profile PROFILE-DEMO and the scenario count-cycle, which sets data variable 2001 to 7 and then clears it:

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,...}

Open the saved trace in the GUI with File > Open Trace…. To only validate the file, use --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 fills the send buffer on purpose and checks the recovery:

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

The detail values go through transport-fault-armed, first-send-accepted, second-send-limit-exceeded, transport-fault-released, transport-recovered and retry-send-accepted, and the run ends with "outcome":"passed". The second send is rejected at the limit; after the fault is released, the same request goes through. In your own program, when you get Backpressure, process the received events first and retry a bounded number of times.

Check the exit code, and keep the output so you can see why a run failed:

터미널 창
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