TrainRBCSim
ADR-029 Train (EVC) simulator — dual-homed TCP client speaking the RBC wire protocol
Loading...
Searching...
No Matches
ertms_adapters.py
Go to the documentation of this file.
1"""Train sim's own adapters between message_catalog.json's flat
2{field: int} fields dict (the same shape every JSON sendMessage command
3already uses for the flat scheme) and rbc_ertms_wire.py's real, per-
4message-shaped encode_msg_*()/decode() functions - injected into
5SimCore's MessageCatalog at construction time (see that module's own
6header doc for why this lives here, not there: Subset-026 is the
7Train<->RBC air-gap protocol specifically, not something IL/CTC ever
8speak in this codebase, so it's this sim's own application-layer
9concern, same as message_catalog.json itself already is per-sim).
10"""
11
12from . import rbc_ertms_wire as ertms
13
14
16 # q_scale/q_dirlrbg/q_dlrbg/l_doubtover/l_doubtunder added this session
17 # (ab_gp_ertms_train_packet_codec.c's own Packet 0 wire extension) -
18 # default to "1m scale, nominal direction/orientation, zero doubt
19 # radius" (the least eventful reading a real EVC would report absent
20 # any actual doubt-window/orientation scenario this sim models) rather
21 # than 0/0/0, which would read as Q_SCALE_10CM/Q_DIRLRBG_REVERSE/
22 # Q_DLRBG_REVERSE - real enum values with a real (wrong) meaning, not
23 # an obviously-unset placeholder.
24 return {
25 "q_scale": fields.get("q_scale", ertms.Q_SCALE_1M),
26 "nid_c": fields.get("nid_c", 0),
27 "nid_bg": fields.get("nid_bg", 0),
28 "d_lrbg": fields.get("d_lrbg", 0),
29 "q_dirlrbg": fields.get("q_dirlrbg", ertms.Q_DIRLRBG_NOMINAL),
30 "q_dlrbg": fields.get("q_dlrbg", ertms.Q_DLRBG_NOMINAL),
31 "l_doubtover": fields.get("l_doubtover", 0),
32 "l_doubtunder": fields.get("l_doubtunder", 0),
33 "v_train": fields.get("v_train", 0),
34 "m_mode": fields.get("m_mode", 0),
35 "m_level": fields.get("m_level", 0),
36 }
37
38
40 """Same shape as _position_report_from_fields() plus Packet 1's own
41 extra NID_PRVLRBG pair (prv_nid_c/prv_nid_bg) - defaults to mirroring
42 nid_c/nid_bg when not explicitly given, since a stationary heartbeat's
43 "previous" LRBG is typically its own last-reported one."""
44 base = _position_report_from_fields(fields)
45 base["prv_nid_c"] = fields.get("prv_nid_c", base["nid_c"])
46 base["prv_nid_bg"] = fields.get("prv_nid_bg", base["nid_bg"])
47 return base
48
49
50# Catalog message name -> (nid_engine, flat_fields_dict, t_train) -> bytes.
51ENCODERS = {
52 "M155": lambda nid_engine, fields, t_train: ertms.encode_msg_155(nid_engine, t_train),
53 "M156": lambda nid_engine, fields, t_train: ertms.encode_msg_156(nid_engine, t_train),
54 "M159": lambda nid_engine, fields, t_train: ertms.encode_msg_159(
55 nid_engine, fields.get("m_version", 0), t_train),
56 "M146": lambda nid_engine, fields, t_train: ertms.encode_msg_146(
57 nid_engine, fields.get("t_train_ack", 0), t_train),
58 "M136": lambda nid_engine, fields, t_train: ertms.encode_msg_136(
59 nid_engine, _position_report_from_fields(fields), t_train),
60 "M136_P1": lambda nid_engine, fields, t_train: ertms.encode_msg_136_packet1(
61 nid_engine, _position_report_packet1_from_fields(fields), t_train),
62 "M132": lambda nid_engine, fields, t_train: ertms.encode_msg_132(
63 nid_engine, fields.get("q_marqstreason", 0), _position_report_from_fields(fields), t_train),
64 "M129": lambda nid_engine, fields, t_train: ertms.encode_msg_129(
65 nid_engine, _position_report_from_fields(fields), fields.get("l_train", 0),
66 fields.get("v_maxtrain", 0), fields.get("m_loadinggauge", 0), fields.get("m_axleload", 0),
67 bool(fields.get("m_airtight", 0)), t_train),
68 "M130": lambda nid_engine, fields, t_train: ertms.encode_msg_130(
69 nid_engine, _position_report_from_fields(fields), t_train),
70 "M147": lambda nid_engine, fields, t_train: ertms.encode_msg_147(
71 nid_engine, fields.get("nid_em", 0), fields.get("q_emergency_stop", 0),
72 _position_report_from_fields(fields), t_train),
73 "M150": lambda nid_engine, fields, t_train: ertms.encode_msg_150(
74 nid_engine, _position_report_from_fields(fields), t_train),
75 "M154": lambda nid_engine, fields, t_train: ertms.encode_msg_154(nid_engine, t_train),
76 "M157": lambda nid_engine, fields, t_train: ertms.encode_msg_157(
77 nid_engine, fields.get("q_status", 0), _position_report_from_fields(fields), t_train),
78 "M158": lambda nid_engine, fields, t_train: ertms.encode_msg_158(
79 nid_engine, fields.get("nid_textmessage", 0), _position_report_from_fields(fields), t_train),
80}
81
82
83def _flatten_msg3(decoded):
84 """Msg 3 (Movement Authority) -> one flat {field: value} dict, same
85 shape decode_to_message() already returns for the flat scheme, plus
86 "packetsPresent" (this message's own optional-packet list) so a
87 Robot test can assert presence, not just field values. Packet-
88 specific fields are prefixed by packet (gradient_*/ssp_*/linking_*)
89 since e.g. gradient and ssp both have their own "segment_count" -
90 Msg 3 in this flow ALWAYS carries both at once (Packet 15+21+27),
91 so this collision is real, not theoretical."""
92 flat = dict(decoded["ma"])
93 if decoded["has_gradient"]:
94 flat["gradient_segment_count"] = decoded["gradient"]["segment_count"]
95 flat["gradient_segments"] = decoded["gradient"]["segments"]
96 if decoded["has_ssp"]:
97 flat["ssp_segment_count"] = decoded["ssp"]["segment_count"]
98 flat["ssp_segments"] = decoded["ssp"]["segments"]
99 if decoded["has_linking"]:
100 flat["linking_link_count"] = decoded["linking"]["link_count"]
101 flat["linking_links"] = decoded["linking"]["links"]
102 flat["packetsPresent"] = decoded["packets_present"]
103 return flat
104
105
106def _flatten_msg24(decoded):
107 """Msg 24 (General Message) -> one flat dict, same shape/rationale as
108 _flatten_msg3() above. NOTE a real, currently-latent collision: P65
109 (tsr) and P66 (tsr_revoke) both have their own "nid_tsr" field - not
110 prefixed below since this project's own SoM/Running/EoM flow never
111 carries both in the same Msg 24, but a future scenario that does
112 (dynamic TSR work) would need to prefix these the same way
113 gradient/ssp are above."""
114 flat = {}
115 if decoded["has_national_values"]:
116 flat.update(decoded["national_values"])
117 if decoded["has_ma_request_params"]:
118 flat.update(decoded["ma_request_params"])
119 if decoded["has_pr_params"]:
120 flat.update(decoded["pr_params"])
121 if decoded["has_tsr"]:
122 flat.update(decoded["tsr"])
123 if decoded["has_tsr_revoke"]:
124 flat.update(decoded["tsr_revoke"])
125 if decoded["has_text_message"]:
126 flat.update(decoded["text_message"])
127 flat["packetsPresent"] = decoded["packets_present"]
128 return flat
129
130
131# nid_message (int) -> decoded_dict -> flat_fields_dict.
132FLATTENERS = {
133 ertms.MSG_MOVEMENT_AUTHORITY: _flatten_msg3,
134 ertms.MSG_GENERAL_MESSAGE: _flatten_msg24,
135}
_position_report_from_fields(fields)
_position_report_packet1_from_fields(fields)