Skip to content
Start free evaluation

First communication

In this tutorial, you:

  • Run an equipment and a host interactively and watch the HSMS connection state and the GEM communication state change.
  • Request status variable names and values and read the SECS-II bodies.
  • Send online and offline requests and read their acknowledgement codes.
  • See what happens to a request the equipment doesn’t handle.
  • Connect the same host to the equipment in the simulator GUI.
  • Finish Set up your environment and open two terminals with the environment variables set.
  • Have the C++ example build/pdcomm-tutorial and the C# example build/managed/bin/PDComm.Tutorial/release/PDComm.Tutorial.dll built.

The commands use a Linux or macOS shell. On Windows, replace "$PDCOMM_ROOT/build/pdcomm-tutorial" with & "$Root\build\pdcomm-tutorial.exe" and "$PDCOMM_NATIVE" with "$Native".

In the first terminal, start the equipment in passive mode:

터미널 창
"$PDCOMM_ROOT/build/pdcomm-tutorial" equipment passive 127.0.0.1:5000
LISTEN 127.0.0.1:5000
Commands: state, establish, identity, values, names, online, offline, linktest, quit

The equipment registers SVID 1 (Temperature, F4 21.5) and SVID 2 (Lot, ASCII LOT-001) and starts in the HostOffline control state.

In the second terminal, start the C# host in active mode. Besides the C++ commands, the C# example also sends directory, alarm list and formatted process program requests.

터미널 창
dotnet "$PDCOMM_ROOT/build/managed/bin/PDComm.Tutorial/release/PDComm.Tutorial.dll" "$PDCOMM_NATIVE" host active 127.0.0.1:5000

When the Commands: line appears, the connection and the Select procedure are done. Type the commands in the following steps one at a time, and wait for each result.

state
establish
state
STATE tcp=Selected gem=NotCommunicating
RESULT kind=2 request=1 outcome=2 status=CommunicationEstablished
SECS-II 0102210100010241065044436F6D6D4105302E312E30
STATE tcp=Selected gem=Communicating
  • After the TCP connection and the HSMS Select, GEM is still NotCommunicating. It becomes Communicating only after the host sends S1F13 and receives an S1F14 that carries COMMACK 0.
  • The C# example prints state names; the C++ example prints numbers. tcp=4 means Selected, gem=1 NotCommunicating and gem=3 Communicating.

The equipment terminal shows the result of the S1F13 it received. kind=1 marks a received primary and outcome=2 means communication was established.

RESULT kind=1 request=0 outcome=2
SECS-II 01 00
names 1 2
values 2 1
values 9
RESULT kind=2 request=4 outcome=6 status=NamesReceived
SECS-II 01020103B10400000001410B54656D70657261747572654101430103B1040000000241034C6F744100
RESULT kind=2 request=3 outcome=5 status=ValuesReceived
SECS-II 010241074C4F542D303031910441AC0000
RESULT kind=2 request=3 outcome=5 status=ValuesReceived
SECS-II 01010100
  • names 1 2 sends S1F11. The S1F12 body holds one L[3] { U4 SVID, A name, A units } per SVID. Temperature has the units C; Lot has empty units.
  • values 2 1 sends S1F3. The values in S1F4 come back in the order you asked for: here A "LOT-001" first, then F4 21.5.
  • For an SVID the equipment doesn’t know, such as values 9, the equipment returns a zero-length list L[0] (01 00) in that position. That isn’t an error, so always check the format and length of each value.
online
offline
RESULT kind=2 request=6 outcome=12 status=OnlineAck
SECS-II 210100
RESULT kind=2 request=5 outcome=11 status=OfflineAck
SECS-II 210100

online sends S1F17 and offline sends S1F15. The reply body 21 01 00 is B 0x00: ONLACK 0 (accepted) and OFLACK 0 (acknowledged). In the equipment terminal, outcome=8 (OnlineAccepted) and outcome=7 (OfflineAccepted) show that the equipment’s control state really changed.

The communication state and the control state are separate. Equipment can be offline while it’s Communicating, and remote commands may also require a control state such as OnlineRemote. For details, see GEM state models.

linktest
state

linktest sends the HSMS control message Linktest.req. It isn’t a GEM request, so no RESULT line appears. Without a reply, the connection drops after T6, so if the following state still shows tcp=Selected, the linktest succeeded. The simulator’s Message Trace & Diagnostics workspace shows the round trip itself.

7. Send requests the equipment doesn’t handle

Section titled “7. Send requests the equipment doesn’t handle”

The tutorial equipment has no alarm catalog and no event directory. Type these in the host:

alarms
directories

The host prints:

RESULT kind=3 request=13 outcome=0 status=None
RESULT kind=2 request=21 outcome=36 status=DataDirectoryReceived
SECS-II 0100
RESULT kind=3 request=22 outcome=0 status=None

The equipment prints:

RESULT kind=5 request=0 outcome=0
SECS-II 01 00
ABORT S5F0 for S5F5
RESULT kind=1 request=0 outcome=38
SECS-II 01 00
RESULT kind=5 request=0 outcome=0
SECS-II 01 00
ABORT S1F0 for S1F23
  • alarms sends S5F5 (list alarms request). The equipment’s GEM layer reports kind=5 (Unhandled). The SDK never answers an unhandled request on its own, so the example answers every unhandled request that expects a reply (W-bit set) with the abort message S5F0 and prints an ABORT line.
  • The host receives S5F0 and ends the request at once with kind=3 (Failed). Without any reply, the host would wait until T3 expires.
  • directories sends S1F21 (data directory) and S1F23 (event directory). The equipment handles S1F21 and returns an empty list; it aborts S1F23.

Here’s the C# example, with that part highlighted:

examples/tutorial/PDComm.Tutorial/Program.cs
using System.Collections.Concurrent;
using PDComm;
if (args.Length is < 4 or > 5 || args[1] is not ("host" or "equipment") ||
args[2] is not ("active" or "passive") || args.Length == 5 && args[4] != "--auto")
{
Console.Error.WriteLine("Usage: PDComm.Tutorial <absolute-library-path> <host|equipment> <active|passive> <numeric-IP:port> [--auto]");
return 2;
}
try
{
PdcommRuntime.Initialize(Path.GetFullPath(args[0]));
bool automatic=args.Length==5; var mode=args[2]=="active"?HsmsMode.Active:HsmsMode.Passive;
using GemEndpoint endpoint=args[1]=="host"?
GemHost.Create(args[3],TutorialOptions.Transport(mode),TutorialOptions.HostOptions()):
GemEquipment.Create(args[3],TutorialOptions.Transport(mode),TutorialOptions.EquipmentOptions());
var api=endpoint.FormattedPrograms; TutorialOptions.Configuration().Configure(endpoint);
using var id=SecsItem.FromAscii("PP-DEMO",TutorialOptions.Limits);
using var code=SecsItem.FromAscii("RUN",TutorialOptions.Limits);
var payload=new GemFormattedProgramPayload(id,"PDComm","0.1.0",[new(code,[])]);
if(endpoint is GemEquipment equipment)
{
api.SeedQuery(payload);
using var temperature=SecsItem.FromF4([21.5f],TutorialOptions.Limits);
using var lot=SecsItem.FromAscii("LOT-001",TutorialOptions.Limits);
equipment.RegisterStatus(1,"Temperature","C",temperature); equipment.RegisterStatus(2,"Lot","",lot);
api.ReplaceCapabilities("PDComm","0.1.0",4,4096,[TutorialOptions.Capability()]);
}
endpoint.Start();
if(mode==HsmsMode.Passive) Console.WriteLine($"LISTEN {endpoint.GetLocalAddress()}");
var input=new ConcurrentQueue<string>();
if(!automatic)
{
Console.WriteLine("Commands: state, establish, identity, values, names, online, offline, linktest, directories, alarms, transfer, query, verify-request, verify-report, capability, negotiate, store, dryrun, execute, quit");
_=Task.Run(()=> { string? line; while((line=Console.ReadLine()) is not null) { input.Enqueue(line); if(line=="quit") return; } input.Enqueue("quit"); });
}
int step=0,received=0; DateTime deadline=DateTime.UtcNow.AddSeconds(20),finish=deadline; bool finishing=false,quit=false;
while(!quit)
{
endpoint.Poll(); HsmsEvent? current;
while((current=endpoint.NextEvent()) is not null)
{
using(current) using(var result=endpoint.HandleEvent(current))
{
var info=result.GetInfo();
if(info.Kind!=GemResultKind.Ignored)
{
Console.WriteLine($"RESULT kind={(uint)info.Kind} request={(uint)info.RequestKind} outcome={(uint)info.Outcome} status={info.Outcome}");
using var body=result.GetBody(); if(body is not null) Console.WriteLine("SECS-II "+Convert.ToHexString(body.Encode()));
}
if(info.Kind==GemResultKind.Unhandled && current.GetInfo().Header is { ReplyExpected: true } primary)
{
// No GEM handler took this primary. Abort it with SxF0 so the peer does not
// wait for T3 and the primary does not keep holding an incoming slot.
try
{
endpoint.ReplyRaw(current,primary.Stream,0,false,ReadOnlySpan<byte>.Empty);
Console.WriteLine($"ABORT S{primary.Stream}F0 for S{primary.Stream}F{primary.Function}");
}
catch(PdcommException error) { Console.Error.WriteLine($"REPLY ERROR status={error.Status}"); }
}
if(info.Kind==GemResultKind.Primary)
{
switch(info.RequestKind)
{
case GemRequestKind.FormattedTransfer: api.RespondTransferAndStore(result,0,GemFormattedProgramStoreAction.Upsert); break;
case GemRequestKind.FormattedQuery: api.RespondQuery(result); break;
case GemRequestKind.FormattedVerificationRequest: api.RespondVerificationRequest(result,0); break;
case GemRequestKind.FormattedVerificationReport: api.RespondVerificationReport(result); break;
case GemRequestKind.FormattedCapability: api.RespondCapabilityQuery(result); break;
case GemRequestKind.FormattedVerificationNegotiation: api.RespondVerification(result,0); break;
}
if(automatic && endpoint is GemEquipment && ++received==3) { finishing=true; finish=DateTime.UtcNow.AddMilliseconds(500); }
}
if(automatic && endpoint is GemHost host && info.Kind==GemResultKind.Response)
{
if(step==1 && info.Outcome==GemOutcome.CommunicationEstablished) { host.AreYouOnline(); step=2; }
else if(step==2 && info.Outcome==GemOutcome.EquipmentIdentity) { host.RequestStatusValues([1,2]); step=3; }
else if(step==3 && info.Outcome==GemOutcome.ValuesReceived) { finishing=true; finish=DateTime.UtcNow.AddMilliseconds(500); }
}
}
}
if(automatic && endpoint is GemHost autoHost && step==0 && endpoint.GetTransportState().State==HsmsConnectionState.Selected) { autoHost.BeginCommunication(); step=1; }
if(automatic && finishing && DateTime.UtcNow>=finish) break;
if(automatic && DateTime.UtcNow>deadline) throw new TimeoutException("Tutorial peer response deadline expired");
if(input.TryDequeue(out string? line))
{
try
{
uint[] ids=[1,2];
if(line.StartsWith("values ",StringComparison.Ordinal) || line.StartsWith("names ",StringComparison.Ordinal))
{
string[] fields=line.Split(' ',StringSplitOptions.RemoveEmptyEntries);
ids=fields.Skip(1).Select(uint.Parse).ToArray(); line=fields[0];
}
if(line=="quit") quit=true;
else if(line=="state") Console.WriteLine($"STATE tcp={endpoint.GetTransportState().State} gem={endpoint.GetState().Communication}");
else if(line=="linktest") endpoint.Linktest();
else if(line=="transfer") api.SubmitTransfer(payload);
else if(line=="query") api.SubmitQuery(id);
else if(line=="verify-request") api.SubmitVerificationRequest(payload);
else if(line=="verify-report") api.SubmitVerificationReport(id,[new(1,1,"demo issue")]);
else if(line=="capability") api.SubmitCapabilityQuery();
else if(line=="negotiate") { using var prepared=api.PrepareVerification(id,[]); api.SubmitPreparedVerification(prepared); }
else if(line=="store") { api.PutProgram(payload,GemFormattedProgramStoreAction.Upsert); Console.WriteLine($"STORE entries={api.GetStoreInfo().EntryCount}"); }
else if(line is "dryrun" or "execute")
{
var selection=api.SelectProgram(id); using var report=api.DryRun(selection,new(1,32,8192));
Console.WriteLine($"DRYRUN outcome={(GemFormattedProgramDryRunOutcome)report.GetInfo().Outcome} issues={report.GetInfo().IssueCount}");
if(line=="execute")
{
using var start=api.StartExecution(selection,report);
api.SubmitActionResult(GemFormattedPrograms.GetActionToken(start.Action),GemFormattedProgramActionResult.Accepted);
using var action=api.StepExecution(start.Token);
Console.WriteLine("APPLICATION DEMO: acknowledge RUN without controlling equipment");
api.SubmitActionResult(GemFormattedPrograms.GetActionToken(action),GemFormattedProgramActionResult.Applied);
api.CompleteExecution(start.Token,1,GemFormattedProgramTerminalOutcome.Complete,ReadOnlyMemory<byte>.Empty);
using var snapshot=api.GetExecutionSnapshot(); Console.WriteLine($"EXECUTION state={(GemFormattedProgramExecutionState)snapshot.GetInfo().State}");
}
}
else if(endpoint is GemHost host)
{
switch(line)
{
case "establish": host.BeginCommunication(); break;
case "identity": host.AreYouOnline(); break;
case "values": host.RequestStatusValues(ids); break;
case "names": host.RequestStatusNames(ids); break;
case "online": host.RequestOnline(); break;
case "offline": host.RequestOffline(); break;
case "directories": host.RequestDataDirectory([]); host.RequestEventDirectory([]); break;
case "alarms": host.RequestAlarmList([]); break;
default: throw new ArgumentException("Unknown command");
}
}
else throw new ArgumentException("This command requires the Host role");
}
catch(Exception error) { Console.Error.WriteLine("COMMAND ERROR "+error.Message); }
}
Thread.Sleep(2);
}
endpoint.Stop(); if(automatic) Console.WriteLine($"PASS tutorial {args[1]} {args[2]}"); return 0;
}
catch(Exception error) { Console.Error.WriteLine(error); return 1; }

Type quit in both terminals.

The role (host or equipment) and the connection mode (active or passive) are independent. This time the C# host waits and the C++ equipment connects:

터미널 창
dotnet "$PDCOMM_ROOT/build/managed/bin/PDComm.Tutorial/release/PDComm.Tutorial.dll" "$PDCOMM_NATIVE" host passive 127.0.0.1:5000
터미널 창
"$PDCOMM_ROOT/build/pdcomm-tutorial" equipment active 127.0.0.1:5000

Type establish and values 1 2 in the host terminal; the results match the earlier ones. On the equipment side, state starts at STATE tcp=4 gem=1. If both sides are active, or both are passive, nobody listens or nobody connects, and no communication happens.

9. Connect to the equipment in the simulator GUI

Section titled “9. Connect to the equipment in the simulator GUI”

Run the simulator as the equipment and connect the C++ host to it.

  1. Start the simulator: & "$Simulator\pdcomm-simulator.exe" on Windows, "$PDCOMM_SIMULATOR/pdcomm-simulator" from a desktop session on Linux, or pdcomm-simulator.app on macOS.
  2. In Overview, under Endpoint configuration, set GEM role to Equipment, TCP mode to Passive - listen, Address to 127.0.0.1:5000 and Session ID to 7.
  3. Click Start session. The TCP TRANSPORT card shows Listening.
  4. In the second terminal, start the C++ host and type establish and values 1001 1002:
터미널 창
"$PDCOMM_ROOT/build/pdcomm-tutorial" host active 127.0.0.1:5000
RESULT kind=2 request=1 outcome=2
SECS-II 01 02 21 01 00 01 02 41 10 50 44 43 6f 6d 6d 20 53 69 6d 75 6c 61 74 6f 72 41 01 31
RESULT kind=2 request=3 outcome=5
SECS-II 01 02 41 04 49 44 4c 45 b1 04 00 00 00 00

The simulator equipment reports the model name PDComm Simulator and software revision 1. Its built-in status variables are SVID 1001 EquipmentState (ASCII IDLE) and SVID 1002 WaferCount (U4 0). They differ from SVIDs 1 and 2 of the tutorial equipment, so ask for values 1001 1002.

Simulator Overview workspace for the Equipment role, listening and then communicatingSimulator Overview workspace for the Equipment role, listening and then communicating
  1. In the simulator, open Status & Data, select SVID 1002, enter 5 in Value and click Apply value.
  2. Type values 1001 1002 in the host again. The second value changes to b1 04 00 00 00 05 (U4 5).
Status & Data workspace listing the equipment status variables 1001 and 1002Status & Data workspace listing the equipment status variables 1001 and 1002
  1. Type quit in the host, then click Stop in the simulator.

The simulator GUI guide covers every workspace.

Symptom Cause and fix
COMMAND ERROR PDComm native operation failed with status InvalidState. The session isn’t Selected yet, or GEM communication isn’t established. Check with state and run establish first.
COMMAND ERROR This command requires the Host role You typed a host command in the equipment terminal.
COMMAND ERROR Unknown command The command name is wrong. The C++ example doesn’t support directories, alarms or the formatted program commands.
Every value in values is 01 00. The equipment doesn’t have those SVIDs. The tutorial equipment uses 1 and 2; the simulator uses 1001 and 1002.
You connect to the simulator, but RESULT kind=3 request=1 appears only five seconds (T3) after establish. The simulator’s Session ID isn’t 7. With different HSMS session IDs, the peer doesn’t process data messages. Use the same value on both sides.
Every request ends with RESULT kind=3 after T3. The peer neither handles nor answers the requests. The PDComm simulator’s equipment aborts unhandled requests with SxF0 at once, so you won’t see this with it. See Connection and timeout problems.

Type quit in every terminal. In the simulator, click Stop and close the window.