SimCore
Shared transport-layer plumbing for the ADR-029 Train/IL/CTC RBC simulators
Loading...
Searching...
No Matches
sim_config.py
Go to the documentation of this file.
1"""JSON-file sim configuration loader. Replaces the old per-variable
2os.environ reads (RBC_WEST_HOST, RBC_WEST_TRAIN_PORT, RBC_TRAIN_INDEX,
3...) that used to be set directly in docker-compose.yml's own
4`environment:` block for each service.
5
6One JSON file per sim INSTANCE, not per sim type - train-west and
7train-east need different values (trainIndex, which RBC ports) even
8though they run the exact same Docker image (safeapi-sim:latest, see
9docker-compose.yml's own header on why one image is reused for every
10instance) - so the file is supplied at container-start time via a
11bind-mounted path (SIM_CONFIG_PATH env var, default
12DEFAULT_CONFIG_PATH), not baked into the image. See
13sims/config/*.json for the actual per-instance files and
14docker-compose.yml's own `volumes:` entries for how each gets mounted.
15
16Schema (fields present depend on sim type - train/il have `trainIndex`,
17ctc does not; see each sim's own header for its exact expected keys):
18 {
19 "siteTag": "WEST", # display tag only, log prefix
20 "trainIndex": 0, # train/il only - 0 or 1
21 "rbcWest": {"host": "c-west", "port": 15004},
22 "rbcEast": {"host": "c-east", "port": 15014},
23 "controlServer": {"bindHost": "0.0.0.0", "port": 9100},
24 "simPeriodSeconds": 2.0 # train only
25 }
26"""
27
28import json
29import os
30
31DEFAULT_CONFIG_PATH = "/app/config.json"
32
33
34def load():
35 """@return the parsed JSON config dict from SIM_CONFIG_PATH (env,
36 default DEFAULT_CONFIG_PATH). Raises with the path included on a
37 missing file or invalid JSON, so a bad config fails loudly and
38 specifically at startup rather than as a generic KeyError somewhere
39 inside main() once a sim tries to use a field that was never
40 there."""
41 path = os.environ.get("SIM_CONFIG_PATH", DEFAULT_CONFIG_PATH)
42 try:
43 with open(path, "r", encoding="utf-8") as f:
44 text = f.read()
45 except OSError as exc:
46 raise FileNotFoundError(
47 f"sim config file not found/readable: {path} ({exc}) - set SIM_CONFIG_PATH to override"
48 ) from None
49 try:
50 return json.loads(text)
51 except json.JSONDecodeError as exc:
52 raise ValueError(f"sim config file {path} is not valid JSON: {exc}") from None
53
54
55def require(config, *keys):
56 """@return config[keys[0]][keys[1]]... - raises a clear KeyError
57 (naming the full dotted path and the config file it came from) if
58 any level is missing, rather than Python's own bare KeyError(key)
59 for just the last, most confusing level."""
60 value = config
61 for i, key in enumerate(keys):
62 if not isinstance(value, dict) or key not in value:
63 dotted = ".".join(keys[: i + 1])
64 raise KeyError(f"sim config is missing required field {dotted!r}")
65 value = value[key]
66 return value
require(config, *keys)
Definition sim_config.py:55