Interlocking (IL) sim (ADR-029) - a real, dual-homed TCP client that
sets/releases routes for one train's session, ON COMMAND from a test
harness (tests/robot/) rather than a fixed wall-clock schedule - see
control_server.py's own header for the command channel design. Dual-homed
the same way train_sim.py is (see dual_link.py's own header) - sends
ROUTE_ADD/ROUTE_RELEASE to both sites unconditionally, since only
whichever site is currently ONLINE will actually act on it.
IL<->RBC route-identity pass: routes are now identified on the wire by
their own (start_signal, end_signal) pair (the real signal pair a route
runs between - see rbc_wire_types.h's own doc), not a synthetic id, and
IL now genuinely RECEIVES something meaningful too -
TrainPositionInRoute, RBC's own report of whether a route currently has
a train authorized through it. Before this pass IL was send-only in
practice (see this file's own git history) - the main loop's own
poll_recv() drained and ignored everything; it now decodes real inbound
messages the same way train_sim.py/ctc_sim.py already do.
Configuration: one JSON file (common/sim_config.py, SIM_CONFIG_PATH env
var, default /app/config.json) supplies everything instance-specific -
trainIndex (0 or 1, selects which of this project's
SAFEAPI_EXAMPLE_MAX_TRAINS train slots this IL instance sets routes for -
train_id is trainIndex + 1; il-west sets routes for train-west's session,
il-east for train-east's, by this shared convention - ADR-029),
siteTag, rbcWest/rbcEast, controlServer, and routeLengthM (the default
ADD_ROUTE grant, replaces the old RBC_ROUTE_LENGTH_M constant). See
sims/il/config/*.json for the actual files. Message vocabulary comes
from message_catalog.json, right next to this script.
Commands (controlServer.port, config file - see common/control_server.py):
ADD_ROUTE [length_m] Sends one ROUTE_ADD (default length
config's own routeLengthM if omitted) - no route
identity, see this handler's own doc for why
that's a real limitation now. Replies "OK\\n".
PING Replies "PONG\\n" - liveness check for a test
harness before it starts asserting anything.
SET_IL_STATUS <s> Sets + sends this IL's own RBC_MSG_IL_STATUS
(s = down|restarting|up). IL announces "up" at
startup and re-announces every
ROUTE_STATUS_PERIOD_S; this overrides it for
tests of the RBC's DOWN/RESTARTING handling.
Replies "OK\\n".
Structured JSON commands (tests/robot/design/DESIGN.md,
SIM_INTERFACES.md - same port, see common/control_server.py's own doc):
sendMessage {"cmd":"sendMessage","nidEngine":N,"message":"ROUTE_ADD",
"fields":{"route_len":500,"start_signal":100,
"end_signal":101,"route_type":0}}
Sends one ROUTE_ADD/ROUTE_RELEASE/etc NOW with the given
fields - the structured equivalent of ADD_ROUTE above
(route_len defaults to config's own routeLengthM if the
field is left unset for ROUTE_ADD, same as ADD_ROUTE's
own default behavior; route identity fields have no
default - the caller must supply them for a real grant).
message "IL_ROUTE_CMD" / "IL_STATUS" use the dedicated
packed IL protocol (il_wire.py, mirrors ab_ga_il_wire.h)
instead of the flat codec: IL_ROUTE_CMD fields
route_type/route_status/degraded_status/
release_route_request/first_route_in_path (enum ints,
first value = default) + route_id/start_signal/end_signal
(-1 = unset); IL_STATUS field il_status (0/1/2).
getMessage {"cmd":"getMessage","nidEngine":N,"message":"TrainPositionInRoute"}
Returns the most recently received packet of that kind
for that nidEngine, or an ERR if none has arrived yet -
same "most recent value, not a queue" contract
train_sim.py's own getMessage already has (DESIGN.md
section 4.2).