hsms-gs: worked integration example + INTEGRATION.md §7

The codebase has supported HSMS-GS since the original landing
(test_hsms_gs.cpp covers the wire-level Select.req-per-session
walk-list, the per-session Reject(EntityNotSelected) behaviour,
and session-routed data dispatch).  But the documentation said
exactly one line about it ("Connection::add_session(device_id)
registers extra sessions on one TCP socket") and there was no
end-to-end test using the Server/Client API customers actually
build against.

INTEGRATION.md §7 is a new section showing the realistic pattern:

- Server-side: register the primary session via Server::Config,
  then `add_session` for the second MES in the on_connection
  callback.  Per-session message handler + selected handler so
  each MES gets its own router (or its own per-session data view
  over a shared EquipmentDataModel).
- Active-mode: same `add_session` on the host-side Connection
  for multi-tool fleet controllers.
- Equipment-initiated push: pick the session_id when sending
  unsolicited primaries (S5F1, S6F11, S10F1).
- Pointer to the wire tests + the new integration test for
  customers who want to see the failure modes.

tests/test_hsms_gs_integration.cpp drives two MES sessions
(device_id 1 + 2) through the Server/Client API end to end:
- Both sessions complete Select.req independently
- S1F1 sent on each session returns a distinct MDLN
  ("EQUIP-SESS-1" vs "EQUIP-SESS-2"), proving per-session
  dispatch routes correctly
- Per-session router fires exactly once per session, no
  cross-talk

Pre-existing §§8-10 in INTEGRATION.md got bumped to §§9-11 to
make room.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 14:56:15 +02:00
parent e3765a5176
commit b99d84f956
3 changed files with 248 additions and 4 deletions
+82 -4
View File
@@ -533,7 +533,85 @@ production.
---
## 7. Recommended layout for a vendor application
## 7. HSMS-GS: one tool, multiple MES
Most fab tools talk to one MES. Some — particularly tools shared by
multiple production lines or sites — need to serve two or more MES
schedulers simultaneously over a single HSMS-GS connection. E37 §11
calls these "general sessions": one TCP socket, multiple session
identifiers, independent SELECTED state per session.
The library models this as additional sessions on the same
`hsms::Connection`:
```cpp
server.on_connection([](std::shared_ptr<Connection> conn) {
// Primary session (device_id=1) was registered by Server::Config;
// add a second session for the second MES.
conn->add_session(/*device_id=*/2);
// Per-session message routing — each MES gets a distinct dispatcher,
// distinct SVID views, distinct alarm enable state, distinct
// recipe namespace if you want. Or share state via a common
// EquipmentDataModel and just route messages here.
conn->set_session_message_handler(1, [model_1](const secs2::Message& m) {
return router_1.dispatch(m);
});
conn->set_session_message_handler(2, [model_2](const secs2::Message& m) {
return router_2.dispatch(m);
});
// Per-session SELECT state observers. These fire when each MES
// completes its Select.req handshake; independent of each other.
conn->set_session_selected_handler(1, [] {
log("MES-1 selected");
});
conn->set_session_selected_handler(2, [] {
log("MES-2 selected");
});
});
```
When the equipment emits an unsolicited primary (S5F1, S6F11,
S10F1), choose the session explicitly:
```cpp
// Alarm goes to MES-1 only.
conn->send_data(/*session_id=*/1, gem::s5f1_alarm_report(0x84, 1, "high"));
// Event report goes to both.
auto event = gem::s6f11_event_report(0, 300, reports);
conn->send_data(1, event);
conn->send_data(2, event);
```
### Active-mode (host side) GS
The host (active) connection initiates Select.req for each registered
session serially — session 1 first, then once 1 reaches SELECTED,
session 2. Customers building a multi-tool fleet controller use the
same `add_session` API on the `Client`-derived `Connection`:
```cpp
client.on_connection([](std::shared_ptr<Connection> conn) {
conn->add_session(2); // a second tool's session
conn->set_session_selected_handler(1, [] { /* tool A ready */ });
conn->set_session_selected_handler(2, [] { /* tool B ready */ });
});
```
### Rejection semantics
A data frame whose `session_id` field doesn't match any registered
session gets a Reject(EntityNotSelected) response, per E37 §7.7 — the
peer's MES will see this and know to back off. See
`tests/test_hsms_gs.cpp` for the wire-level coverage and
`tests/test_hsms_gs_integration.cpp` for the end-to-end Server/Client
pattern.
---
## 8. Recommended layout for a vendor application
```
/opt/acme-secsgem/
@@ -564,7 +642,7 @@ EXECUTING / PAUSE / …) up to the tool builder. Copy
---
## 8. Test the integration
## 9. Test the integration
Don't ship without:
@@ -591,7 +669,7 @@ Don't ship without:
---
## 9. When to extend the runtime
## 10. When to extend the runtime
The library is open to extension. Common reasons to add code:
@@ -613,7 +691,7 @@ contribution.
---
## 10. Going from "stack" to "certified GEM tool"
## 11. Going from "stack" to "certified GEM tool"
This codebase passes its own conformance harness and cross-validates
against `secsgem-py`, but a real *certified* GEM tool needs more: