SimCore
Shared transport-layer plumbing for the ADR-029 Train/IL/CTC RBC simulators
Loading...
Searching...
No Matches
status_report.py
Go to the documentation of this file.
1"""ADR-038 own-perspective status heartbeat for a sim.
2
3Each sim instance reports its OWN view of the world - which RBC sites it
4is currently connected to, plus a few caller-supplied extras - once at
5startup and then every PERIOD_S seconds. Each report is BOTH logged
6locally (so `each sim shows connection statuses from own perspective`,
7ADR-038) AND pushed to both sites over the sim's existing dual-homed
8link as an opaque RBC_MSG_SIM_STATUS envelope frame; the owning Role C
9gateway (train/il/ctc handler) recognises that kind, does NOT forward it
10to A/B, and republishes the ASCII payload to the C-side status broker.
11
12Kind-blind by construction: the caller owns what the payload keys mean;
13this module only handles the cadence, the log line and the send. Mirrors
14rbc_wire.encode_opaque()'s own transport-vs-application split.
15"""
16
17import time
18
19from simcore import rbc_wire
20
21#: rbc_wire_types.h RBC_MSG_SIM_STATUS - kept in sync with BOTH C copies.
22SIM_STATUS_KIND = 19
23
24#: Startup + every PERIOD_S (ADR-038: "Startup + every 5s").
25PERIOD_S = 5.0
26
27
29 """Cadence + encode + log + send for one sim's status heartbeat."""
30
31 def __init__(self, log, link, unit, log_source, kind=SIM_STATUS_KIND,
32 period_s=PERIOD_S, clock=time.monotonic, frame_size=rbc_wire.ENVELOPE_SIZE):
33 """@param frame_size: passed straight through to rbc_wire.encode_opaque() -
34 override if @link's own channel uses a wider fixed slot than the
35 default ENVELOPE_SIZE (see that function's own doc)."""
36 self._log = log
37 self._link = link
38 self._unit = unit
39 self._source = log_source
40 self._kind = kind
41 self._period = period_s
42 self._clock = clock
43 self._frame_size = frame_size
44 self._next = 0.0
45
46 def _payload(self, extra):
47 sites = set(self._link.connected_sites())
48 west_up = "WEST" in sites
49 east_up = "EAST" in sites
50 if west_up and east_up:
51 conn_desc = "Both_Up"
52 elif west_up:
53 conn_desc = "West_Up"
54 elif east_up:
55 conn_desc = "East_Up"
56 else:
57 conn_desc = "Both_Down"
58
59 parts = [
60 f"unit={self._unit}",
61 "up=1",
62 f"west={1 if west_up else 0}",
63 f"east={1 if east_up else 0}",
64 f"conn_status={conn_desc}",
65 ]
66 for key, value in (extra or {}).items():
67 parts.append(f"{key}={value}")
68 return " ".join(parts)
69
70 def emit(self, extra=None):
71 """Report now, unconditionally - use once at startup."""
72 payload = self._payload(extra)
73 self._log.info("[GENERAL] [%s] [internal] [STATUS] [self] [%s]", self._source, payload)
74 self._link.send_all(rbc_wire.encode_opaque(self._kind, payload, frame_size=self._frame_size))
75 self._next = self._clock() + self._period
76
77 def tick(self, extra=None):
78 """Report iff PERIOD_S has elapsed since the last report."""
79 if self._clock() >= self._next:
80 self.emit(extra)
__init__(self, log, link, unit, log_source, kind=SIM_STATUS_KIND, period_s=PERIOD_S, clock=time.monotonic, frame_size=rbc_wire.ENVELOPE_SIZE)