Files
secs-gem/README.md
T
raphael 96b02f8b50 Initial commit: C++20 SECS-II / HSMS / GEM client + server
A fully containerised SECS/GEM toolchain. Single docker compose project,
no host build tools. 63 unit-test cases / 278 assertions, two demo
executables, end-to-end two-container demo exercising every implemented
capability.

Architecture (bottom-up):

  secs2/   E5 SECS-II codec
    Item        variant over L/A/B/BOOLEAN/I1-8/U1-8/F4/F8
    encode/decode  big-endian, 1/2/3-byte length encoding
    Message     SxFy + W-bit + optional root item
    to_sml      human-readable text rendering

  hsms/    E37 HSMS transport (TCP)
    Header      10-byte header + SType enum (Data/Select/Deselect/
                Linktest/Reject/Separate)
    Frame       4-byte length prefix + payload encode/decode
    Connection  async Asio TCP, NOT-SELECTED -> SELECTED state machine,
                T3/T5/T6/T7/T8 timers, system-bytes reply correlation,
                graceful close-after-flush separation

  endpoint  active Client (connect with T5 retry) and passive Server
            (accept loop) wrappers over Connection

  gem/     E30 GEM logic
    ControlStateMachine  5-state E30 control model with operator
                         actions, host requests, SEMI-mandated ack
                         codes (OnlineAck, OfflineAck, CommAck), and
                         a state-change handler
    EquipmentDataModel   in-memory dictionary: SVIDs, DVIDs, ECIDs
                         (with EAC), CEIDs, report defs, CEID->report
                         links, enabled-events set, alarm table
                         (ALCD, enabled, active), process programs,
                         host command registry, clock (16-char
                         YYYYMMDDhhmmsscc with offset)
    messages.hpp         builders + parsers for every SxFy below

GEM message coverage (full list):

  S1F1/F2    Are You There / On Line Data
  S1F3/F4    Selected Equipment Status Request / Data
  S1F11/F12  Status Variable Namelist Request / Data
  S1F13/F14  Establish Communications (+ CommAck)
  S1F15/F16  Request OFFLINE (+ OfflineAck)
  S1F17/F18  Request ONLINE (+ OnlineAck)
  S2F13/F14  Equipment Constant Request / Data
  S2F15/F16  EC Send + EquipmentAck (Accept/UnknownEcid/Busy/OutOfRange)
  S2F17/F18  Date and Time Request / Data
  S2F29/F30  Equipment Constant Namelist Request / Data
  S2F31/F32  Date and Time Set Request / TimeAck
  S2F33/F34  Define Report + DefineReportAck (5 enum values)
  S2F35/F36  Link Event Report + LinkEventAck
  S2F37/F38  Enable / Disable Event Report + EnableEventAck
  S2F41/F42  Host Command + HostCmdAck (7 values) + per-param CPACKs
  S5F1/F2    Alarm Report Send + AlarmAck (ALCD bit-7 set/cleared
             + lower-7 category)
  S5F3/F4    Enable/Disable Alarm Send + AlarmAck
  S5F5/F6    List Alarms Request / Data (active alarms tagged in ALCD)
  S6F11/F12  Event Report Send (equipment-initiated CEID emission
             with full report data) + EventReportAck
  S7F3/F4    Process Program Send + ProcessProgramAck (7 values)
  S7F5/F6    Process Program Request / Data
  S7F19/F20  Current EPPD List Request / Data
  S10F1/F2   Terminal Display Single (host->equipment) + TerminalAck
  S10F3/F4   Terminal Display Single (equipment->host)

Demo apps:

  apps/secs_server.cpp   passive equipment. Populates the data model
                         with 3 SVIDs (ControlState, Clock,
                         EventsEnabled), 2 ECIDs, 3 CEIDs
                         (ControlStateChanged, AlarmSetEvent,
                         ProcessStarted), 2 alarms (Chiller Temp High
                         cat 4, Door Open cat 1), 2 recipes
                         (RECIPE-A, RECIPE-B), and 4 host commands
                         (START, STOP, PAUSE, FAULT). Emits S6F11 on
                         every control state transition + on START;
                         emits S5F1 + the AlarmSetEvent CEID on FAULT.
                         Pushes an S10F3 welcome message when the host
                         comes online.

  apps/secs_client.cpp   active host. Walks 17 steps: Establish ->
                         Online -> S1F11 SVID namelist -> S1F3 read ->
                         S2F29 EC namelist -> S2F13 read ->
                         S2F17 clock -> S2F33/S2F35/S2F37 dynamic
                         event subscription -> S2F41 START
                         (-> receives S6F11) -> S5F5 alarm list ->
                         S5F3 enable alarm 1 -> S2F41 FAULT
                         (-> receives S5F1 + S6F11) -> S7F19/S7F5
                         recipe list + body -> S10F1 terminal ->
                         S1F15 Offline -> Separate. Handles inbound
                         S6F11, S5F1, S10F3 primaries.

Testing:

  tests/test_secs2.cpp         codec round-trip for every format,
                               byte-layout assertions for known values,
                               truncation/trailing-byte rejection,
                               nested list round-trip, SML rendering
  tests/test_hsms.cpp          header byte layout, data + control
                               header round-trip, full frame round-
                               trip with length prefix, short-payload
                               rejection
  tests/test_control_state.cpp every (state, event) pair in the E30
                               control state machine, including
                               AlreadyOnline / NotAccept rejections
                               and idempotent offline-while-offline
  tests/test_data_model.cpp    SVID/ECID/Alarm/Recipe CRUD, clock
                               format + parse, host command registry,
                               full event-report pipeline (define ->
                               link -> enable -> compose) with
                               every error path (InvalidVid,
                               UnknownCeid, UnknownRptid), alarm
                               set/clear with ALCD bit-7 semantics
  tests/test_messages.cpp      round-trip + byte-layout for every
                               builder/parser pair, including S6F11
                               event reports with mixed item types

Toolchain:

  Dockerfile          Ubuntu 24.04, g++-13, CMake, Ninja, libasio-dev
  docker-compose.yml  builder / tests / server / client services,
                      source bind-mounted, build artifacts in a
                      named volume so the host tree stays clean
  CMakeLists.txt      C++20, -Wall -Wextra -Wpedantic, standalone
                      Asio (ASIO_STANDALONE), doctest via FetchContent

Documentation:

  README.md           architecture, quick start, demo log
  COMPLIANCE.md       honest per-capability E5/E30/E37 audit with
                      spec section refs. Calls out what's implemented,
                      what's partial (Reject.req, Alarms missing F7/F8,
                      EC range validation, PP without verify, terminal
                      single-line only), and what's intentionally not
                      yet implemented (spooling, S9 error stream,
                      Documentation S1F19/F20+F21/F22, limits monitoring,
                      trace data collection, multi-block, material
                      movement). Does NOT claim "100% GEM-compliant" and
                      lists the work required to honestly make that claim.

This is Layer 0 + the start of Layer 1 from implementation_plan.md.
The transition-table-driven "spec-as-data" architecture (Layer 1
proper) is not yet implemented; the current code uses imperative
state machines that are structurally ready to be refactored onto
tables.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 00:21:10 +02:00

166 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# secs-gem
A C++20 SECS-II / HSMS / GEM client + server toolchain, fully containerized.
The long-term vision is the "spec-as-data" stack described in
[`implementation_plan.md`](implementation_plan.md): the SEMI E30 behavioral
spec encoded as transition tables, from which a runtime, passive analyzer,
simulator, and conformance test generator are derived. The current code is
the **Layer 0 + Layer 1 base** — a working HSMS client/server, the SECS-II
codec, the E30 control state machine, and the GEM core capabilities (status
variables, equipment constants, dynamic event reporting, alarms, remote
control, clock, process programs, terminal services).
For a per-capability E5/E30/E37 audit see [COMPLIANCE.md](COMPLIANCE.md).
## Quick start
Everything runs in Docker — no compiler or build tools on the host.
```bash
docker compose run --rm builder # configure + compile
docker compose run --rm tests # run unit tests (63 cases / 278 assertions)
docker compose up --no-deps server client # live two-container demo
```
## Architecture
```
┌──────────────────────────────────────────────────────────────┐
│ app / demo │
│ apps/secs_server.cpp apps/secs_client.cpp │
└────────────┬───────────────────────────┬─────────────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ secsgem::gem (E30 / E5 logic) │
│ data_model.hpp SVIDs, DVIDs, ECIDs, CEIDs, alarms, │
│ reports, links, recipes │
│ control_state.h E30 control state machine │
│ messages.hpp all SxFy builders + parsers │
└────────────┬─────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ secsgem::hsms (E37 transport) │
│ Connection async TCP, state machine, T3/T5/T6/T7/T8 │
│ Frame, Header 4-byte length prefix + 10-byte header │
│ Server, Client endpoint wrappers (in include/secsgem/) │
└────────────┬─────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ secsgem::secs2 (E5 codec) │
│ Item variant over L/A/B/BOOLEAN/I1-8/U1-8/F4/F8 │
│ encode/decode big-endian, length-byte aware │
│ Message SxFy + W-bit + optional root Item │
│ to_sml human-readable text for logs │
└──────────────────────────────────────────────────────────────┘
```
### Tree
```
secs-gem/
├── Dockerfile, docker-compose.yml # toolchain + demo
├── CMakeLists.txt
├── implementation_plan.md # the 7-layer spec-as-data roadmap
├── COMPLIANCE.md # per-capability E5/E30/E37 audit
├── include/secsgem/
│ ├── secs2/{item,codec,message}.hpp
│ ├── hsms/{types,header,frame,connection}.hpp
│ ├── gem/{control_state,data_model,messages}.hpp
│ └── endpoint.hpp
├── src/{secs2,hsms,gem,endpoint.cpp}/*.cpp
├── apps/{secs_server,secs_client}.cpp
└── tests/test_*.cpp
```
## What's implemented
### HSMS (E37)
SELECT/DESELECT/LINKTEST/SEPARATE/REJECT control messages, the 4-byte
length-prefixed frame, the 10-byte header, the NOT-CONNECTED → SELECTED
state machine, and the T3/T5/T6/T7/T8 timers. Active (client) and passive
(server) modes. System-bytes based reply correlation.
### SECS-II (E5)
A variant-based `Item` covering every standard format (List, ASCII,
Binary, Boolean, I1I8, U1U8, F4, F8), big-endian numerics, 1/2/3-byte
length encoding, and an `to_sml()` rendering for readable logs.
### GEM (E30)
- **Control state machine** — 5 states (EquipmentOffline, AttemptOnline,
HostOffline, OnlineLocal, OnlineRemote), operator actions, host
requests with SEMI-mandated ACK codes, change-handler callback.
- **Status variables** — S1F3/F4, S1F11/F12.
- **Equipment constants** — S2F13/F14, S2F15/F16, S2F29/F30 + EAC enum.
- **Clock** — S2F17/F18, S2F31/F32 (16-char `YYYYMMDDhhmmsscc`).
- **Establish Comms** — S1F13/F14 + `CommAck`.
- **Online/Offline** — S1F15/F16 + `OfflineAck`, S1F17/F18 + `OnlineAck`.
- **Remote control** — S2F41/F42 + 7-value `HostCmdAck` + per-param CPACKs.
- **Dynamic event reporting** — S2F33 Define Report (DRACK), S2F35 Link
Event (LRACK), S2F37 Enable (ERACK), **S6F11 emission**, S6F12 ack.
- **Alarms** — S5F1/F2 emit + ACKC5, S5F3/F4 enable/disable, S5F5/F6
list, ALCD bit-7 set/cleared, lower-7 category.
- **Process programs** — S7F3/F4 send, S7F5/F6 request, S7F19/F20 list.
- **Terminal services** — S10F1/F2 host→equipment, S10F3/F4 equipment→host.
### Not (yet) implemented
Spooling, S9 error stream, S1F19/F20 + S1F21/F22 documentation, multi-block,
trace data collection (S2F23/F24, S6F1/F2), limits monitoring, material
movement (E40), EC range validation, S5F7/F8 list-enabled-alarms,
S6F15/F16 event-report request. See [COMPLIANCE.md](COMPLIANCE.md) for
the honest accounting.
## Demo session
The demo client walks 17 distinct steps end-to-end. From the live log
(condensed):
```
[host] -> Select.req [equip] <- Select.req
[host] <- Select.rsp (Ok) [equip] -> Select.rsp (Ok)
[host] == SELECTED == [equip] == SELECTED ==
[host] -> S1F13 W [equip] S1F13; replying S1F14 (COMMACK=0)
[host] -> S1F17 W [equip] control: HostOffline -> AttemptOnline
[equip] control: AttemptOnline -> OnlineRemote
[host] S1F18 ONLACK=0 [equip] -> S10F3 W (welcome)
[host] TERMINAL[0] Welcome from SECSGEM-SIM
[host] -> S1F11 W [equip] S1F11; replying S1F12 (status namelist)
[host] SVID 1 ControlState
[host] SVID 2 Clock
[host] SVID 3 EventsEnabled
[host] -> S1F3 W [equip] S1F3; replying S1F4 (3 values)
[host] -> S2F29 W [equip] -> S2F30 (2 EC entries)
[host] -> S2F33 W [equip] -> S2F34 (DRACK=0)
[host] -> S2F35 W [equip] -> S2F36 (LRACK=0)
[host] -> S2F37 W [equip] -> S2F38 (ERACK=0)
[host] -> S2F41 W START [equip] RCMD START; emit S6F11 CEID=300
[host] EVENT CEID=300 (1 reports)
[host] RPTID 1000: <A "OnlineRemote"> <A "2026060122162336"> <BOOLEAN T>
[host] -> S5F5 W [equip] -> S5F6 (2 alarms)
[host] -> S5F3 W [equip] -> S5F4 (ACKC5=0)
[host] -> S2F41 W FAULT [equip] RCMD FAULT triggers alarm 1
[equip] emit S5F1 alarm set ALID=1
[equip] emit S6F11 CEID=200
[host] ALARM SET ALID=1 cat=4 "Chiller Temp High"
[host] EVENT CEID=200 (1 reports)
[host] -> S7F19 W [equip] -> S7F20 (2 PPIDs)
[host] -> S7F5 W [equip] -> S7F6 RECIPE-A
[host] -> S10F1 W [equip] TERMINAL[0] Hello equipment!
[host] -> S1F15 W [equip] control: OnlineRemote -> HostOffline
[host] -> Separate.req [equip] <- Separate.req
[host] exiting
```
## Build details
The toolchain image (`Dockerfile`) is Ubuntu 24.04 with `g++-13`, CMake,
Ninja, and `libasio-dev`. doctest is fetched via CMake `FetchContent`.
Build artifacts live in a named Docker volume (`build:`) so the host
filesystem stays clean.
Standalone Asio is used in header-only mode (`ASIO_STANDALONE`). No Boost
dependency.