Files
secs-gem/docs/40_building_running_demo.md
T
raphael af1a159c59 docs: bring the documentation up to the daemon/client era
A large gap had opened between the docs and the code: the README and
INTEGRATION guide did not mention the gRPC daemon or the Python client at
all (the entire vendor surface), ARCHITECTURE still described secs_server
as the ~1200-line canonical wiring example (it is a ~110-line thin main
over EquipmentRuntime), and test counts across six files were stale
(445/2753 -> 473/3087 core + the separate 125-assertion daemon suite).

- README: new "Integrating your tool (pick a tier)" section — Python
  client / any-language gRPC / embedded C++ — plus daemon tests and
  tools/run_interop.sh in the Testing section.
- ARCHITECTURE: layer diagram gains the vendor-surface and
  EquipmentRuntime/default_handlers tiers; stale wiring row fixed.
- INTEGRATION: three-tier chooser up front (this guide = the C++ tier).
- ch30 tour: secs_gemd + secs_gemd_tests in the binaries table.
- ch31: example alarm used a nonexistent `alcd:` field with bit 7 set
  (which the validator forbids) -> real `category:`/`name:` fields, and
  the roles: block documented.
- ch35: handler-location note now points at default_handlers.cpp's 15
  per-capability register_* functions.
- ch40: built-artifacts list + sample output counts.
- ch50: secsgem::gem runtime/default_handlers/handler_slot/name_index
  includes + new secsgem::daemon namespace section.
- PROOFS: test-count table gains the runtime/handlers/daemon row so the
  tally adds up; daemon suite noted. VERIFICATION/COMPLIANCE counts.
- interop/README: the one-command runner + the two daemon-track harnesses
  (daemon_interop, pyclient_interop).

Audited via a docs-vs-code sweep (the audit itself under-reported: it
validated counts textually; reality was 473/3087).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:18:31 +02:00

9.5 KiB
Raw Blame History

40 — Building, running, the demo

36 Persistence, validation, metrics | Back to index | Next: 41 Integration: hardware, MES, production

You've read about every layer of the codebase. Now we run it.

This chapter is operational: build the project, start the demo, walk what each transaction in the two-container flow actually does and where it lives. By the end you'll have the demo running on your laptop and you'll know what every log line means.


Prerequisites

Just Docker. No host C++ toolchain, no Python deps, nothing to apt-install. The toolchain image (Dockerfile) bundles Ubuntu 24.04 + g++-13 + CMake + Ninja + asio + yaml-cpp + Python 3 + tshark + tcpdump + clang.

docker --version
docker compose version

If both work, you're set.


Building

docker compose run --rm builder

That:

  1. Pulls / builds the toolchain image (first time only, ~3 minutes).
  2. Runs cmake -S /app -B /app/build -G Ninja -DCMAKE_BUILD_TYPE=Release.
  3. Runs cmake --build /app/build.
  4. Produces every binary under /app/build/ inside a named Docker volume.

Subsequent builds are incremental and take ~1030 s.

What got built

build/
├── secs_server          passive equipment (the demo target)
├── secs_client          active host (drives the demo)
├── secs_conformance     47-check conformance harness
├── secs_interop_probe   active host probing secsgem-py equipment
├── secs_bench           throughput/latency bench
├── secsgem_tests        the 473-case doctest binary
├── secs_gemd            gRPC daemon: HSMS equipment + name-based tool API
├── secs_gemd_tests      in-process gRPC service tests (when grpc++ present)
└── pvd_tool             worked PVD-tool example

Plus the generated build/generated/secsgem/gem/messages.hpp (~3 500 lines, auto-derived from data/messages.yaml).


Running the tests

docker compose run --rm tests

Runs secsgem_tests end-to-end. Expected output:

[doctest] doctest version is "2.4.11"
[doctest] run with "--help" for options
===============================================================================
[doctest] test cases:  473 |  473 passed | 0 failed | 0 skipped
[doctest] assertions: 3087 | 3087 passed | 0 failed |
[doctest] Status: SUCCESS!

On a 2024 M-series Mac under Docker Desktop, this takes ~3.5 s.


The two-container demo

docker compose up --no-deps server client

That starts:

  • A server container running secs_server on port 5000.
  • A client container running secs_client against server:5000.

The client drives ~24 SECS transactions through the data model. Each transaction logs on both sides.

What each transaction does

Annotated walk through the log output:

Communication establishment

[host] connecting to server:5000
[equip] accepted connection
[host] sending Select.req
[equip] Select.req received → SELECTED
[host] Select.rsp(Ok) received → SELECTED

HSMS SELECT handshake. Both sides now in SELECTED state.

[host] sending S1F13 Establish Communications
[equip] S1F13 received
[equip] sending S1F14(COMMACK=Accept, [MDLN, SOFTREV])
[host] S1F14 received → COMMUNICATING

E30 §6.5 communication-state transition. Now GEM-level communication is up.

Identification

[host] S1F1 Are You There
[equip] S1F2 ["SECS-GEM Demo Equipment", "1.0.0"]
[host] S1F19 GEM Compliance Request
[equip] S1F20 [list of capabilities]
[host] S1F11 SVID Namelist (all)
[equip] S1F12 [SVID 1 "ControlState", SVID 2 "Clock", ...]
[host] S1F21 DVID Namelist (all)
[equip] S1F22 [DVID list]
[host] S1F23 CEID Namelist (all)
[equip] S1F24 [CEID → VID mapping]

Host walks the data dictionary.

Dynamic event report setup

[host] S2F33 DefineReport(RPTID=1, VIDs=[SVID 2])
[equip] S2F34(DRACK=0)
[host] S2F35 LinkEvent(CEID=300 → [RPTID=1])
[equip] S2F36(LRACK=0)
[host] S2F37 EnableEvent(CEED=true, CEIDs=[300])
[equip] S2F38(ERACK=0)

The three-message report wiring. CEID 300 now triggers an S6F11 when it fires.

Control state + remote command

[host] S2F41 RCMD=START
[equip] S2F42(HCACK=Accept)
[equip] HostCommandRegistry dispatched START
[equip]   → emit CEID 300
[equip]   → compose_reports_for(300) → RPTID 1 = [Clock SV2]
[equip]   → fire S6F11
[equip] S6F11(CEID=300, [RPTID=1, [Clock]])
[host] S6F12(ACKC6=0)

Host command dispatch + event report emission + acknowledgement. This is the canonical GEM transaction.

Alarms

[host] S5F5 List all alarms
[equip] S5F6 [ALID list with ALCD + ALTX]
[host] S5F3 EnableAlarm(ALID=1)
[equip] S5F4(ACKC5=0)
[host] S2F41 RCMD=FAULT
[equip] S2F42(HCACK=Accept)
[equip]   → set ALID 1
[equip]   → fire S5F1(ALCD=0x84, ALID=1)
[equip] S5F1(...)
[host] S5F2(ACKC5=0)

Recipes

[host] S7F1 PP Load Inquire(PPID="NEW-RECIPE", LENGTH=64)
[equip] S7F2(PPGNT=0=Permit)
[host] S7F3 PP Send(PPID="NEW-RECIPE", PPBODY=<bytes>)
[equip] S7F4(ACKC7=0)
[host] S7F5 PP Request(PPID="NEW-RECIPE")
[equip] S7F6 [PPID, PPBODY]
[host] S7F17 PP Delete(PPIDs=["NEW-RECIPE"])
[equip] S7F18(ACKC7=0)

Terminal display

[host] S10F3 Terminal Display Multi (TID=0, TEXT="hello\nfrom host")
[equip] S10F4(ACKC10=0)

Clean shutdown

[host] S1F15 Request Offline
[equip] S1F16(OFLACK=Accept)
[host] sending Separate.req
[equip] Separate.req received → close

Total: 24 transactions exercising S1, S2, S5, S6, S7, S10.


Running the conformance harness

docker compose up -d server
docker compose run --rm builder /app/build/secs_conformance --host server --port 5000
docker compose down

Runs the 47-check conformance harness against the demo server. Each check covers one E30 / GEM 300 wire-level behaviour:

[PASS] E37 §7.2 SELECT handshake
[PASS] E30 §6.5 S1F13/F14 Establish Comms
[PASS] E30 §6.7 S1F1/F2 Are You There
... (43 more)
[PASS] E30 §6.10 S1F19/F20 GEM Compliance

  47 / 47 checks passed

This is proof #2 in docs/PROOFS.md.


Running the interop sweeps

secsgem-py

docker compose up -d server
docker compose run --rm interop python3 /app/interop/host_vs_cpp_server.py --host server
docker compose down

The Python secsgem-py 0.3.0 host drives our equipment. 31 checks across S1/S2/S5/S6/S7/S10 + unsolicited S6F11 / S5F1.

secs4java8

bash interop/secs4j_validate.sh

The Java secs4java8 host drives our equipment via a separate container. 55 checks covering S1/S2/S3/S5/S6/S7/S10/S14/S16 including the GEM 300 streams that secsgem-py couldn't easily drive.

tshark dissector

docker compose run --rm builder bash /app/interop/tshark_validate.sh

Captures a pcap of the demo flow, dissects with Wireshark's HSMS dissector, asserts no malformed packets. 69 frames, 0 errors.

libFuzzer (60 s, requires clang)

docker compose run --rm builder bash -c "
  cmake -S /app -B /app/build-fuzz -G Ninja -DSECSGEM_FUZZ=ON \
        -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
  cmake --build /app/build-fuzz
  /app/build-fuzz/fuzz_secs2_decode -max_total_time=60
  /app/build-fuzz/fuzz_sml_parse -max_total_time=60
"

200 k+ inputs through secs2::decode, 1.4 M+ through try_parse_sml, ASan + UBSan clean, 0 crashes.

All five sweeps are wired into CI; see .gitea/workflows/ci.yml.


Inspecting the demo from outside

While the demo is running, you can:

Watch the wire

# In another shell:
docker compose exec server tcpdump -i any -A -s 0 'tcp port 5000'

Inspect with tshark + HSMS dissector

docker compose run --rm builder tshark -i any -d "tcp.port==5000,hsms" -V \
    | grep -A 2 "Header"

Watch the metrics

pvd_tool example exposes a Prometheus endpoint:

docker compose run --rm --service-ports builder /app/build/pvd_tool \
    /app/examples/pvd_tool/equipment.yaml \
    /app/data/control_state.yaml \
    5000 9090

Then curl localhost:9090/metrics.


Running the bench

docker compose run --rm builder /app/build/secs_bench \
    --requests 50000 --concurrency 32 --svid-count 32

Outputs a markdown table of throughput + p50/p95/p99 latencies for:

  • S1F1/F2 (header-only round-trip).
  • S1F3/F4 with 32 SVIDs.
  • S6F11 push (W=0, fire-and-forget).
  • PJ + CJ memory footprint.

See docs/BENCHMARKS.md for the baseline numbers and capacity-planning notes.


Reading the source while it runs

A common workflow when you're learning:

  1. docker compose up --no-deps server client in one shell.
  2. Source viewer open in another (your IDE on the host — the source isn't bind-mounted in the container, but it is on your host).
  3. Find a log line that confuses you (e.g. [equip] S6F11 fired).
  4. Grep the source for it. Most log strings are unique enough to land in the right file in one search.
  5. Read the function around it.
  6. Cross-reference back to the chapter that covers the standard.

This is the most efficient way to internalise the codebase. The demo runs forever (until you Ctrl-C — the client loops); you can read the source at your own pace.


Where to go next

You now have the demo running and you can drive any of the five external validators. The next chapter is the integration chapter — wiring the runtime to real hardware, talking to a real MES, production deployment, security, performance tuning.

Next: → 41 Integration: hardware, MES, production