SEMI E4 · E5 · E30 · E37
SECS/GEM
Build GEM equipment and hosts that communicate over HSMS and SECS-I, with SECS-II messages and the GEM capabilities for both sides.
What it does
- HSMS-SS (SEMI E37, E37.1) over TCP/IP, active or passive, with the T3, T5, T6, T7 and T8 timers you set.
- SECS-I (SEMI E4) through your read and write callbacks, so the same code drives a COM port, a virtual serial port or an in-memory line.
- GEM communication and control state models (S1F13, S1F15, S1F17).
- Data collection: status and data variables, event reports (S2F33, S2F35, S2F37, S6F11) and equipment constants.
- Alarms (S5F1–S5F8) and remote commands (S2F41, S2F49) checked against your command catalog.
- Process programs (S7F1–S7F6, S7F17–S7F32) and spooling (S2F43, S6F23).
- A C ABI that creates no threads of its own; .NET worker classes run the poll loop for you.
Example
Each binding shows code from a real example file in the examples folder of the PDComm SDK. The source file and line range appear above the code.
SECS/GEM host and equipment in C
while (!done && !failed && time(NULL) < deadline) {
for (int side = 0; side < 2; ++side) {
size_t progressed = 0;
failed |= !ok(pdcomm_tcp_poll(endpoints[side], &progressed), "pdcomm_tcp_poll");
while (!failed) {
PdcommTcpEvent *event = NULL;
failed |= !ok(pdcomm_tcp_next_event(endpoints[side], &event), "pdcomm_tcp_next_event");
if (!event) break;
PdcommGemResult *result = NULL;
PdcommGemResultInfo info = {0};
info.struct_size = sizeof(info);
if (ok(pdcomm_gem_handle_event(endpoints[side], event, &result), "pdcomm_gem_handle_event") &&
ok(pdcomm_gem_result_info(result, &info), "pdcomm_gem_result_info") &&
endpoints[side] == host && info.kind == PDCOMM_GEM_RESPONSE) {
/* Each reply of the host's requests leads to the next request. */
if (info.outcome == PDCOMM_GEM_COMMUNICATION_ESTABLISHED) {
puts("S1F14: communication established");
failed |= !ok(pdcomm_gem_host_request(host, PDCOMM_GEM_ARE_YOU_ONLINE, NULL, 0, &token),
"pdcomm_gem_host_request");
} else if (info.outcome == PDCOMM_GEM_EQUIPMENT_IDENTITY) {
puts("S1F2: equipment identity received");
failed |= !ok(pdcomm_gem_host_request(host, PDCOMM_GEM_STATUS_VALUES, ids, 2, &token),
"pdcomm_gem_host_request");
} else if (info.outcome == PDCOMM_GEM_VALUES_RECEIVED) {
for (size_t index = 0; index < info.entry_count; ++index) print_status_value(result, index);
done = info.entry_count == 2;
failed |= !done;
} else {
fprintf(stderr, "Unexpected outcome %u\n", (unsigned)info.outcome);
failed = 1;
}
}
pdcomm_gem_result_destroy(result);
pdcomm_tcp_event_destroy(event);
}
}
if (!began && tcp_state(host, NULL) == PDCOMM_TCP_SELECTED) {
began = 1;
failed |= !ok(pdcomm_gem_begin_communication(host, &token), "pdcomm_gem_begin_communication");
}
pause_one_millisecond();
}From the C example: one thread polls the equipment and host endpoints and hands every event to pdcomm_gem_handle_event(). The host establishes communication (S1F13), asks for the equipment identity (S1F1), then reads two status values (S1F3).
SECS/GEM C++ tutorial
const auto limits = tutorial::itemLimits();
pdcomm::TcpEndpoint transport(argv[3], tutorial::tcpOptions(active ? PDCOMM_TCP_ACTIVE : PDCOMM_TCP_PASSIVE));
std::unique_ptr<pdcomm::GemHost> host;
std::unique_ptr<pdcomm::GemEquipment> equipment;
if (hostRole) host.reset(new pdcomm::GemHost(std::move(transport), pdcomm::gemHostOptions(limits, 64)));
else {
equipment.reset(new pdcomm::GemEquipment(std::move(transport), limits,
pdcomm::GemControl::HostOffline, pdcomm::GemOnlineMode::Remote, "PDComm", "0.1.0", 8, 1024, 4096));
const std::vector<uint8_t> temperature = {0x41, 0xac, 0, 0};
auto value = pdcomm::SecsItem::primitive(PDCOMM_SECS_F4, temperature, limits);
equipment->registerStatus(1, "Temperature", "C", value);
auto lot = pdcomm::SecsItem::primitive(PDCOMM_SECS_ASCII, std::vector<uint8_t>{'L','O','T','-','0','0','1'}, limits);
equipment->registerStatus(2, "Lot", "", lot);
}
pdcomm::GemEndpoint *endpoint = hostRole ? static_cast<pdcomm::GemEndpoint*>(host.get()) : equipment.get();
endpoint->start();
if (!active) std::cout << "LISTEN " << endpoint->tcp().localAddress() << std::endl;From the C++ tutorial used by the SECS/GEM quickstart: create the HSMS transport, create a GemHost or GemEquipment, register two status variables on the equipment and start it.
.NET SECS/GEM demo
static async Task<GemEquipmentWorker> StartEquipmentAsync(
string address,
HsmsOptions transport,
SecsLimits limits,
HsmsWorkerOptions worker,
CancellationToken cancellationToken)
{
using SecsItem temperature = SecsItem.FromF4([21.5f], limits);
using SecsItem lot = SecsItem.FromAscii("LOT-001", limits);
GemStatusVariable[] variables =
[
new(1, "Temperature", "C", temperature),
new(2, "Lot", "", lot),
];
return await GemEquipmentWorker.StartAsync(
address,
transport,
DemoOptions.Equipment(limits),
worker,
variables,
cancellationToken);
}From the .NET SECS/GEM demo: build Temperature and Lot status variables as SecsItems and start a GemEquipmentWorker asynchronously.
SECS/GEM Qt worker
auto endpoint = makeEndpoint();
pdcomm::GemHost host(
std::move(endpoint), pdcomm::gemHostOptions(itemLimits(), 64));
host.start();
bool beginSent = false;
size_t step = 0;
const auto deadline = std::chrono::steady_clock::now() +
std::chrono::seconds(15);
while (step < 9 && std::chrono::steady_clock::now() < deadline &&
!isInterruptionRequested()) {
host.poll();
if (!beginSent &&
host.tcp().state().state == PDCOMM_TCP_SELECTED) {
host.beginCommunication();
beginSent = true;
}
for (auto event = host.nextEvent(); event;
event = host.nextEvent()) {
auto result = host.handle(event);
const auto info = result.info();
if (info.outcome ==
pdcomm::GemOutcome::CommunicationEstablished) {
sendStep(host, 0);
continue;
}
if (info.kind != pdcomm::GemResultKind::Response) continue;
validateHostResult(result, step);
if (step == 1) {
payload.values = std::make_shared<pdcomm::GemResult>(
std::move(result));
} else if (step == 2) {
payload.names = std::make_shared<pdcomm::GemResult>(
std::move(result));
}
++step;
if (step < 9) sendStep(host, step);
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));From the Qt example: SECS/GEM has no Qt-specific header, so a QThread worker owns the C++ GemHost and runs the poll loop. The GUI thread never blocks; once communication is established the worker sends each request and checks the reply.