RTE RBC 2oo2 Generic Application 0.1.0 build 308eda7
IL/CTC route and train logic for the RBC_GP reference RBC
Loading...
Searching...
No Matches
ab_ga_il.c
Go to the documentation of this file.
1
7#include "ab_ga_il.h"
8
9#include <stdio.h>
10#include "rte/oal/memory/rte_mem_util.h"
11#include "rte/utils/buffer/rte_buffer.h"
12#include "rte/utils/cast/rte_cast.h"
13#include "rte/oal/log/rte_log.h"
14#include "ga_interface.h" /* ab_gp_db_*()/ab_gp_train_*() - bare calls now, see that file's own doc */
15#include "ab_ga_il_wire.h" /* dedicated IL messages 25/26/27 - decode + validation */
16#include "ab_ga_route_fsm.h" /* per-route state machine driven by RBC_MSG_IL_ROUTE_CMD */
17
18/* Fixed-offset RBC envelope wire sizes for IL domain (REQ-RBC-001) */
19#define IL_WIRE_HEADER_SIZE 12U
20#define IL_WIRE_CONTENT_SIZE_ROUTE_ADD 24U
21#define IL_WIRE_CONTENT_SIZE_ROUTE_IDENTITY (RBC_ENVELOPE_WIRE_SIZE - IL_WIRE_HEADER_SIZE)
22
26typedef struct
27{
28 uint8_t train_id; /* slot id (== nid_engine in this simplified model), 0 = none */
29 uint16_t route_id;
30 uint32_t start_signal;
31 uint32_t end_signal;
32 uint32_t route_status; /* rbc_route_status_t - NO_TRAIN / TRAIN_IN_ROUTE */
33 bool train_has_ma; /* connected train has an MA reaching into this route */
34 uint8_t fsm_state; /* ab_ga_route_state_t - the RBC's acknowledged per-route state */
35 bool fsm_is_stub; /* fsm_state is a not-yet-implemented OS/SH/DEGRADED stub */
37
38static bool route_fsm_state_is_stub(uint8_t s)
39{
40 return (s == (uint8_t)AB_GA_ROUTE_STATE_OS) || (s == (uint8_t)AB_GA_ROUTE_STATE_SH)
41 || (s == (uint8_t)AB_GA_ROUTE_STATE_DEGRADED);
42}
43
44static il_pending_route_event_t g_pending_route_events[AB_GP_IL_MAX_PENDING_OUTPUTS];
45static uint8_t g_pending_route_event_count = 0U;
46
50static rbc_il_status_t g_il_status = RBC_IL_STATUS_DOWN;
51
52static bool il_wire_decode(const uint8_t in[RBC_ENVELOPE_WIRE_SIZE], rbc_envelope_t *out_env);
53static void il_wire_encode(uint8_t out[RBC_ENVELOPE_WIRE_SIZE], uint8_t train_id, uint32_t start_signal,
54 uint32_t end_signal, uint32_t route_status);
55static bool handle_route_add(ab_gp_db_t *db, const rbc_envelope_t *env, uint32_t cycle, rte_example_site_t site,
56 const char *role_tag);
57static bool handle_route_release(ab_gp_db_t *db, const rbc_envelope_t *env, uint32_t cycle, rte_example_site_t site,
58 const char *role_tag);
59static bool handle_route_status_report(const rbc_envelope_t *env, uint32_t cycle, const char *role_tag);
60static bool handle_il_route_cmd(ab_gp_db_t *db, const ab_ga_il_route_cmd_t *cmd, uint32_t cycle,
61 rte_example_site_t site, const char *role_tag);
62static bool handle_il_status(const ab_ga_il_status_msg_t *st, uint32_t cycle, const char *role_tag);
63static void enqueue_pending_route_event(uint8_t train_id, uint16_t route_id, uint32_t start_signal,
64 uint32_t end_signal, uint32_t route_status, bool train_has_ma,
65 uint8_t fsm_state);
66/* il_on_route_connected/_disconnected forward decls REMOVED -
67 * ab_ga_il_on_route_context_changed() below is now the ONE public
68 * function GA's dispatch module calls, not two GP-registered callbacks -
69 * see this file's own module doc. */
70
71bool ab_ga_il_site_has_route(uint16_t route_id, const ab_site_ga_route_layout_t **out_layout)
72{
73 size_t i;
74
75 if (route_id == 0U)
76 {
77 return false;
78 }
79 for (i = 0U; i < ab_site_ga_track_layout_count; i++)
80 {
81 if (ab_site_ga_track_layout[i].id == route_id)
82 {
83 if (out_layout != NULL)
84 {
85 *out_layout = &ab_site_ga_track_layout[i];
86 }
87 return true;
88 }
89 }
90 return false;
91}
92
93bool ab_ga_il_site_has_route_by_signals(uint16_t start_signal, uint16_t end_signal,
94 const ab_site_ga_route_layout_t **out_layout)
95{
96 size_t i;
97
98 for (i = 0U; i < ab_site_ga_track_layout_count; i++)
99 {
100 if ((ab_site_ga_track_layout[i].start_signal == start_signal)
101 && (ab_site_ga_track_layout[i].end_signal == end_signal))
102 {
103 if (out_layout != NULL)
104 {
105 *out_layout = &ab_site_ga_track_layout[i];
106 }
107 return true;
108 }
109 }
110 return false;
111}
112
113bool ab_ga_il_route_verify(uint16_t route_id, uint16_t start_signal, uint16_t end_signal)
114{
115 const ab_site_ga_route_layout_t *layout = NULL;
116
117 if (!ab_ga_il_site_has_route(route_id, &layout))
118 {
119 return false;
120 }
121 return (layout->start_signal == start_signal) && (layout->end_signal == end_signal);
122}
123
124bool ab_ga_il_route_add(ab_gp_db_t *db, uint16_t route_id, uint16_t start_signal, uint16_t end_signal)
125{
126 if (!ab_ga_il_route_verify(route_id, start_signal, end_signal))
127 {
128 return false;
129 }
130 return ab_gp_db_route_add(db, route_id, start_signal, end_signal);
131}
132
133bool ab_ga_il_route_remove(ab_gp_db_t *db, uint16_t route_id)
134{
135 return ab_gp_db_route_remove(db, route_id);
136}
137
138bool ab_ga_il_route_set_status(ab_gp_db_t *db, uint16_t route_id, ab_gp_db_route_il_status_t status)
139{
140 return ab_gp_db_route_set_status(db, route_id, status);
141}
142
143bool ab_ga_il_route_set_type(ab_gp_db_t *db, uint16_t route_id, uint32_t route_type)
144{
145 return ab_gp_db_route_set_type(db, route_id, route_type);
146}
147
148bool ab_ga_il_route_assign_train(ab_gp_db_t *db, uint16_t route_id, uint8_t train_id)
149{
150 return ab_gp_db_route_connect_train(db, route_id, train_id);
151}
152
153bool ab_ga_il_route_unassign_train(ab_gp_db_t *db, uint16_t route_id)
154{
155 return ab_gp_db_route_disconnect_train(db, route_id);
156}
157
158bool ab_ga_il_handle_route_add(ab_gp_db_t *db, uint8_t train_id)
159{
160 return ab_gp_train_set_has_ma(db, train_id, true);
161}
162
163bool ab_ga_il_apply_envelope(const void *context, ab_gp_train_session_t *sessions, ab_gp_db_t *db,
164 const uint8_t env_bytes[RBC_ENVELOPE_WIRE_SIZE], uint32_t cycle,
165 rte_example_site_t site, const char *role_tag)
166{
167 rbc_envelope_t env_storage;
168 const rbc_envelope_t *env = &env_storage;
169
170 (void)context; /* unused - matches ab_gp_il_envelope_handler_fn's own signature so this
171 * function can be registered directly, no trampoline needed - see this
172 * file's own header doc */
173 (void)sessions; /* unused now - handle_route_add()/handle_route_release() no longer touch
174 * train sessions directly (moved to ab_ga_il_on_route_context_changed(),
175 * see that function's own doc) - kept in this signature only because it's
176 * part of ab_gp_il_envelope_handler_fn's own required shape. */
177
178 /* Dedicated IL messages (25/26/27) have their own packed layout, NOT
179 * the shared rbc_envelope_t il_wire_decode() understands - route them
180 * on the raw kind byte before the legacy decode. */
181 switch (env_bytes[0])
182 {
183 case (uint8_t)RBC_MSG_IL_ROUTE_CMD:
184 {
186 if (!ab_ga_il_wire_decode_route_cmd(env_bytes, &cmd))
187 {
188 rte_log_write_event(RTE_LOG_LEVEL_ERROR, site_config_name(site), cycle, "C", role_tag,
189 "IL_ROUTE_CMD", "malformed or out-of-range", NULL);
190 return false;
191 }
192 return handle_il_route_cmd(db, &cmd, cycle, site, role_tag);
193 }
194 case (uint8_t)RBC_MSG_IL_STATUS:
195 {
197 if (!ab_ga_il_wire_decode_status(env_bytes, &st))
198 {
199 rte_log_write_event(RTE_LOG_LEVEL_ERROR, site_config_name(site), cycle, "C", role_tag,
200 "IL_STATUS", "malformed or out-of-range", NULL);
201 return false;
202 }
203 return handle_il_status(&st, cycle, role_tag);
204 }
205 default:
206 break; /* fall through to the legacy flat-envelope path */
207 }
208
209 if (!il_wire_decode(env_bytes, &env_storage))
210 {
211 RTE_EXAMPLE_LOG("[%s] cycle %u: malformed or unexpected-kind envelope from C (IL relay)\n", role_tag,
212 cycle);
213 rte_log_write_event(RTE_LOG_LEVEL_ERROR, site_config_name(site), cycle, "C", role_tag, "ENVELOPE",
214 "malformed", NULL);
215 return false;
216 }
217
218 switch (env->kind)
219 {
220 case RBC_MSG_ROUTE_ADD:
221 return handle_route_add(db, env, cycle, site, role_tag);
222 case RBC_MSG_ROUTE_RELEASE:
223 return handle_route_release(db, env, cycle, site, role_tag);
224 case RBC_MSG_ROUTE_STATUS_REPORT:
225 return handle_route_status_report(env, cycle, role_tag);
226 default:
227 RTE_EXAMPLE_LOG("[%s] cycle %u: IL relay envelope kind %d unexpected - dropping\n", role_tag, cycle,
228 (int)env->kind);
229 return false;
230 }
231}
232
233uint8_t ab_ga_il_build_outputs(ab_gp_il_pending_output_t out[AB_GP_IL_MAX_PENDING_OUTPUTS])
234{
235 uint8_t events = g_pending_route_event_count;
236 uint8_t i;
237 uint8_t oi = 0U;
238
239 for (i = 0U; i < events; i++)
240 {
241 const il_pending_route_event_t *event = &g_pending_route_events[i];
242 const bool train_in_route = (event->route_status == (uint32_t)RBC_ROUTE_STATUS_TRAIN_IN_ROUTE);
244 uint8_t relay_index = (event->train_id > 0U) ? (uint8_t)(event->train_id - 1U) : 0U;
245
246 /* Frame 1: legacy TRAIN_POSITION_IN_ROUTE - unchanged wire shape,
247 * kept for the existing liveness-first Robot flow. */
248 il_wire_encode(out[oi].bytes, event->train_id, event->start_signal, event->end_signal, event->route_status);
249 (void)snprintf(out[oi].tag, sizeof(out[oi].tag), "TRAIN_POS_ROUTE"); /* fits tag[16]; the old 18-char label was silently cut */
250 out[oi].index = relay_index;
251 oi++;
252
253 /* Frame 2: RBC_MSG_IL_ROUTE_INDICATION - the richer field set.
254 * emergency_in_route / emergency_type are not wired yet (no
255 * per-route emergency tracking) - always "none" for now. */
256 ind.route_id = (int32_t)event->route_id;
257 ind.start_signal = (int32_t)event->start_signal;
258 ind.end_signal = (int32_t)event->end_signal;
259 ind.allocated_train_nid_engine = train_in_route ? (int32_t)event->train_id : -1;
260 ind.train_in_route = train_in_route;
261 ind.train_has_ma_in_route = event->train_has_ma;
262 ind.emergency_in_route = false;
263 ind.emergency_type = RBC_IL_EMERGENCY_TYPE_NONE;
264 ind.route_fsm_state = event->fsm_state;
265 ind.route_fsm_is_stub = event->fsm_is_stub;
266 ab_ga_il_wire_encode_route_indication(out[oi].bytes, &ind);
267 (void)snprintf(out[oi].tag, sizeof(out[oi].tag), "IL_ROUTE_IND");
268 out[oi].index = relay_index;
269 oi++;
270 }
271 return oi;
272}
273
275{
276 g_pending_route_event_count = 0U;
277}
278
279/* ab_ga_il_init() REMOVED - its entire body was two GP registrations,
280 * both now owned by ab_ga_dispatch.c's own single subscription (see this
281 * file's own module doc and ab_ga_dispatch.h). */
282
289static bool il_wire_decode(const uint8_t in[RBC_ENVELOPE_WIRE_SIZE], rbc_envelope_t *out_env)
290{
291 rte_buffer_t buf;
292 rbc_envelope_t decoded;
293 uint16_t l_message;
294 const uint16_t expected_l_message =
295 (uint16_t)(IL_WIRE_HEADER_SIZE + IL_WIRE_CONTENT_SIZE_ROUTE_IDENTITY);
296 bool is_route_add;
297 bool is_route_status_report;
298
299 is_route_add = (in[0] == (uint8_t)RBC_MSG_ROUTE_ADD);
300 is_route_status_report = (in[0] == (uint8_t)RBC_MSG_ROUTE_STATUS_REPORT);
301 if (!is_route_add && !is_route_status_report && (in[0] != (uint8_t)RBC_MSG_ROUTE_RELEASE))
302 {
303 return false;
304 }
305 rte_mem_set(&decoded, 0, sizeof(decoded));
306 decoded.kind = (rbc_msg_kind_t)in[0];
307
308 if (rte_buffer_init(&buf, (uint8_t *)in, RBC_ENVELOPE_WIRE_SIZE) != RTE_STATUS_OK)
309 {
310 return false;
311 }
312 (void)rte_buffer_set_length(&buf, RBC_ENVELOPE_WIRE_SIZE);
313
314 if (rte_buffer_read_u16_le(&buf, 4U, &l_message) != RTE_STATUS_OK)
315 {
316 return false;
317 }
318 if (l_message != expected_l_message)
319 {
320 return false;
321 }
322 decoded.l_message = l_message;
323 decoded.nid_message = in[2];
324 decoded.train_id = in[1];
325
326 if ((rte_buffer_read_u32_le(&buf, 8U, &decoded.t_train) != RTE_STATUS_OK)
327 || (rte_buffer_read_u32_le(&buf, 80U, &decoded.start_signal) != RTE_STATUS_OK)
328 || (rte_buffer_read_u32_le(&buf, 84U, &decoded.end_signal) != RTE_STATUS_OK))
329 {
330 return false;
331 }
332 if (is_route_status_report)
333 {
334 /* Same offset TRAIN_POSITION_IN_ROUTE's own il_wire_encode() writes
335 * route_status at (92U, after start_signal@80/end_signal@84 and a
336 * zero-padding u32 at 88 where ROUTE_ADD's route_type lives instead) -
337 * this is IL originating the SAME wire shape in the OTHER direction. */
338 if (rte_buffer_read_u32_le(&buf, 92U, &decoded.route_status) != RTE_STATUS_OK)
339 {
340 return false;
341 }
342 }
343 if (is_route_add)
344 {
345 if ((rte_buffer_read_u32_le(&buf, 24U, &decoded.route_len) != RTE_STATUS_OK)
346 || (rte_buffer_read_u32_le(&buf, 88U, &decoded.route_type) != RTE_STATUS_OK))
347 {
348 return false;
349 }
350 }
351
352 *out_env = decoded;
353 return true;
354}
355
359static void il_wire_encode(uint8_t out[RBC_ENVELOPE_WIRE_SIZE], uint8_t train_id, uint32_t start_signal,
360 uint32_t end_signal, uint32_t route_status)
361{
362 rte_buffer_t buf;
363 uint16_t l_message = (uint16_t)(IL_WIRE_HEADER_SIZE + IL_WIRE_CONTENT_SIZE_ROUTE_IDENTITY);
364
365 rte_mem_set(out, 0, RBC_ENVELOPE_WIRE_SIZE);
366 (void)rte_buffer_init(&buf, out, RBC_ENVELOPE_WIRE_SIZE);
367 out[0] = (uint8_t)RBC_MSG_TRAIN_POSITION_IN_ROUTE;
368 out[1] = train_id;
369 out[2] = 0U;
370 out[3] = 0U;
371 (void)rte_buffer_set_length(&buf, 4U);
372 (void)rte_buffer_write_u16_le(&buf, l_message);
373 (void)rte_buffer_write_u16_le(&buf, 0U);
374 (void)rte_buffer_write_u32_le(&buf, 0U);
375 (void)rte_buffer_set_length(&buf, 80U);
376 (void)rte_buffer_write_u32_le(&buf, start_signal);
377 (void)rte_buffer_write_u32_le(&buf, end_signal);
378 (void)rte_buffer_write_u32_le(&buf, 0U);
379 (void)rte_buffer_write_u32_le(&buf, route_status);
380}
381
402static bool handle_route_add(ab_gp_db_t *db, const rbc_envelope_t *env, uint32_t cycle, rte_example_site_t site,
403 const char *role_tag)
404{
405 const ab_site_ga_route_layout_t *layout = NULL;
406
407 if (!ab_ga_il_site_has_route_by_signals((uint16_t)env->start_signal, (uint16_t)env->end_signal, &layout))
408 {
409 RTE_EXAMPLE_LOG(
410 "[%s] cycle %u: ROUTE_ADD for unknown route (start_signal=%u end_signal=%u) - dropping\n", role_tag,
411 cycle, (unsigned int)env->start_signal, (unsigned int)env->end_signal);
412 rte_log_write_event(RTE_LOG_LEVEL_ERROR, site_config_name(site), cycle, "IL", role_tag, "ROUTE_ADD",
413 "unknown route", NULL);
414 return false;
415 }
416 if (!ab_gp_db_route_exists(db, layout->id))
417 {
418 (void)ab_ga_il_route_add(db, layout->id, layout->start_signal, layout->end_signal);
419 }
420 (void)ab_ga_il_route_set_type(db, layout->id, env->route_type);
421
422 RTE_EXAMPLE_LOG("[%s] cycle %u: [ONLINE] route %u tracked (type=%u) - MA grant awaits GP's own "
423 "position-based connect\n",
424 role_tag, cycle, (unsigned int)layout->id, (unsigned int)env->route_type);
425 rte_log_write_event(RTE_LOG_LEVEL_INFO, site_config_name(site), cycle, "IL", role_tag, "ROUTE_ADD", "tracked",
426 NULL);
427 return true;
428}
429
444static bool handle_route_release(ab_gp_db_t *db, const rbc_envelope_t *env, uint32_t cycle,
445 rte_example_site_t site, const char *role_tag)
446{
447 const ab_site_ga_route_layout_t *layout = NULL;
448 ab_gp_route_context_t rctx;
449
450 if (!ab_ga_il_site_has_route_by_signals((uint16_t)env->start_signal, (uint16_t)env->end_signal, &layout))
451 {
452 RTE_EXAMPLE_LOG(
453 "[%s] cycle %u: ROUTE_RELEASE for unknown route (start_signal=%u end_signal=%u) - dropping\n", role_tag,
454 cycle, (unsigned int)env->start_signal, (unsigned int)env->end_signal);
455 rte_log_write_event(RTE_LOG_LEVEL_ERROR, site_config_name(site), cycle, "IL", role_tag, "ROUTE_RELEASE",
456 "unknown route", NULL);
457 return false;
458 }
459 if (!ab_gp_db_route_exists(db, layout->id))
460 {
461 RTE_EXAMPLE_LOG("[%s] cycle %u: ROUTE_RELEASE for route %u - not tracked, acknowledging noTrain\n", role_tag,
462 cycle, (unsigned int)layout->id);
463 /* No train is genuinely known for an untracked route - 0, not
464 * env->train_id (see this function's own doc). */
465 enqueue_pending_route_event(0U, layout->id, layout->start_signal, layout->end_signal, (uint32_t)RBC_ROUTE_STATUS_NO_TRAIN, false, (uint8_t)AB_GA_ROUTE_STATE_NO_ROUTE);
466 return true;
467 }
468 /* used_in_ma read now via ab_gp_db_get_route_context() - the
469 * individual _get_used_in_ma() getter this used to call is gone
470 * (later session, on direct request). */
471 (void)ab_gp_db_get_route_context(db, layout->id, &rctx);
472 if (rctx.used_in_ma)
473 {
474 /* Fires AB_ROUTE_DISCONNECTED synchronously (GP's own
475 * ab_gp_db_route_disconnect_train(), notify_route_context()) -
476 * ab_ga_il_on_route_context_changed() does the MA shrink/zero AND
477 * its own enqueue_pending_route_event(), using the route's own
478 * real (pre-clear) train_id - nothing further needed here. */
479 (void)ab_ga_il_route_unassign_train(db, layout->id);
480 }
481 else
482 {
483 enqueue_pending_route_event(0U, layout->id, layout->start_signal, layout->end_signal, (uint32_t)RBC_ROUTE_STATUS_NO_TRAIN, false, (uint8_t)AB_GA_ROUTE_STATE_NO_ROUTE);
484 }
485 (void)ab_ga_il_route_remove(db, layout->id);
486 /* Distinct from "released" below (train association dropped): this
487 * marks the DB entry itself gone (ab_ga_il_route_remove(), just
488 * above) - on direct request, "removing" is its own logged fact,
489 * not folded silently into the release line. */
490 RTE_EXAMPLE_LOG("[%s] cycle %u: route %u removed from DB\n", role_tag, cycle, (unsigned int)layout->id);
491 rte_log_write_event(RTE_LOG_LEVEL_INFO, site_config_name(site), cycle, "IL", role_tag, "ROUTE_REMOVED",
492 "removed", NULL);
493
494 RTE_EXAMPLE_LOG("[%s] cycle %u: route %u released\n", role_tag, cycle, (unsigned int)layout->id);
495 rte_log_write_event(RTE_LOG_LEVEL_INFO, site_config_name(site), cycle, "IL", role_tag, "ROUTE_RELEASE",
496 "released", NULL);
497 return true;
498}
499
515static bool handle_route_status_report(const rbc_envelope_t *env, uint32_t cycle, const char *role_tag)
516{
517 bool known = ab_ga_il_site_has_route_by_signals((uint16_t)env->start_signal, (uint16_t)env->end_signal, NULL);
518
519 RTE_EXAMPLE_LOG(
520 "[%s] cycle %u: ROUTE_STATUS_REPORT start_signal=%u end_signal=%u route_status=%u (route %s)\n", role_tag,
521 cycle, (unsigned int)env->start_signal, (unsigned int)env->end_signal, (unsigned int)env->route_status,
522 known ? "known" : "UNKNOWN to this site's own layout");
523 return true;
524}
525
535static bool handle_il_route_cmd(ab_gp_db_t *db, const ab_ga_il_route_cmd_t *cmd, uint32_t cycle,
536 rte_example_site_t site, const char *role_tag)
537{
538 uint16_t route_id;
539 bool want_route;
541
542 /* Need a concrete route identity to track/step. int32 -1 (or an
543 * out-of-uint16 value) => no specific route => accepted but no-op. */
544 if ((cmd->route_id < 0) || (cmd->route_id > (int32_t)UINT16_MAX))
545 {
546 RTE_LOG_WRITE_EVENT_F(RTE_LOG_LEVEL_WARNING, site_config_name(site), cycle, "IL", role_tag,
547 "IL_ROUTE_CMD",
548 "no route_id (=%d) - command ignored (route_type=%d route_status=%d release=%d)",
549 (int)cmd->route_id, (int)cmd->route_type, (int)cmd->route_status,
550 cmd->release_route_request ? 1 : 0);
551 return true;
552 }
553 route_id = (uint16_t)cmd->route_id;
554
555 want_route = (!cmd->release_route_request) && (cmd->route_type == RBC_IL_ROUTE_TYPE_LOCKED);
556
557 if (want_route)
558 {
559 /* Track the route if the site knows it and it is not tracked yet.
560 * start/end signals are optional in the command (-1); when both
561 * are given, verify + add through the same site-checked path
562 * ROUTE_ADD uses; otherwise, if the route is already tracked,
563 * leave it. */
564 if (!ab_gp_db_route_exists(db, route_id))
565 {
566 const ab_site_ga_route_layout_t *layout = NULL;
567
568 if ((cmd->start_signal >= 0) && (cmd->end_signal >= 0)
570 (uint16_t)cmd->end_signal, &layout)
571 && (layout->id == route_id))
572 {
573 (void)ab_ga_il_route_add(db, route_id, layout->start_signal, layout->end_signal);
574 }
575 else if (ab_ga_il_site_has_route(route_id, &layout))
576 {
577 (void)ab_ga_il_route_add(db, route_id, layout->start_signal, layout->end_signal);
578 }
579 else
580 {
581 RTE_LOG_WRITE_EVENT_F(RTE_LOG_LEVEL_ERROR, site_config_name(site), cycle, "IL", role_tag,
582 "IL_ROUTE_CMD",
583 "route %u (start_signal=%d end_signal=%d) unknown to this site's layout - rejecting",
584 (unsigned int)route_id, (int)cmd->start_signal, (int)cmd->end_signal);
585 return false;
586 }
587 }
588 }
589
590 fsm = ab_ga_route_fsm_step(db, route_id, cmd, cycle, role_tag);
591
592 if ((!want_route) && ab_gp_db_route_exists(db, route_id))
593 {
594 /* Release / NO_ROUTE: untrack. Any connected train is
595 * disconnected through GP's own path (fires
596 * ab_ga_il_on_route_context_changed()'s AB_ROUTE_DISCONNECTED,
597 * which does the MA shrink AND its own indication) - same as
598 * handle_route_release(). */
599 (void)ab_ga_il_route_remove(db, route_id);
600 }
601
602 /* Acknowledge EVERY route command with a fresh RBC_MSG_IL_ROUTE_INDICATION
603 * reflecting the RBC's resulting per-route view - so IL gets feedback
604 * even for a command that connects no train (the connect/disconnect
605 * hook only fires when a train is actually involved). Route identity
606 * always comes from the static site layout, never the (optional)
607 * wire fields. */
608 {
609 const ab_site_ga_route_layout_t *layout = NULL;
610
611 if (ab_ga_il_site_has_route(route_id, &layout))
612 {
613 uint8_t alloc_train = 0U;
614 bool has_ma = false;
615 uint32_t rstatus;
616
617 if (ab_gp_db_route_exists(db, route_id))
618 {
619 ab_gp_route_context_t rctx;
620 if (ab_gp_db_get_route_context(db, route_id, &rctx))
621 {
622 alloc_train = rctx.train_id;
623 has_ma = rctx.used_in_ma;
624 }
625 }
626 rstatus = (alloc_train != 0U) ? (uint32_t)RBC_ROUTE_STATUS_TRAIN_IN_ROUTE
627 : (uint32_t)RBC_ROUTE_STATUS_NO_TRAIN;
628 enqueue_pending_route_event(alloc_train, route_id, layout->start_signal,
629 layout->end_signal, rstatus, has_ma, (uint8_t)fsm.to);
630 }
631 }
632
633 return true;
634}
635
641static bool handle_il_status(const ab_ga_il_status_msg_t *st, uint32_t cycle, const char *role_tag)
642{
643 g_il_status = st->il_status;
644
645 if ((st->il_status == RBC_IL_STATUS_DOWN) || (st->il_status == RBC_IL_STATUS_RESTARTING))
646 {
647 RTE_EXAMPLE_LOG("[%s] cycle %u: IL_STATUS=%s - route state trust HOLD not implemented yet (stub)\n",
648 role_tag, cycle,
649 (st->il_status == RBC_IL_STATUS_DOWN) ? "DOWN" : "RESTARTING");
650 }
651 else
652 {
653 RTE_EXAMPLE_LOG("[%s] cycle %u: IL_STATUS=UP\n", role_tag, cycle);
654 }
655 return true;
656}
657
661static void enqueue_pending_route_event(uint8_t train_id, uint16_t route_id, uint32_t start_signal,
662 uint32_t end_signal, uint32_t route_status, bool train_has_ma,
663 uint8_t fsm_state)
664{
665 uint8_t i;
666 /* Each queued event yields TWO output frames (legacy + indication),
667 * so the effective ceiling is half the array. */
668 const uint8_t max_events = (uint8_t)(AB_GP_IL_MAX_PENDING_OUTPUTS / 2U);
669
670 for (i = 0U; i < g_pending_route_event_count; i++)
671 {
672 if ((g_pending_route_events[i].route_id == route_id) &&
673 (g_pending_route_events[i].train_id == train_id) &&
674 (g_pending_route_events[i].route_status == route_status) &&
675 (g_pending_route_events[i].fsm_state == fsm_state))
676 {
677 return; /* already queued this exact route view this cycle */
678 }
679 }
680 if (g_pending_route_event_count >= max_events)
681 {
682 RTE_EXAMPLE_LOG("IL: pending route-indication queue full - dropping event for train %u\n",
683 (unsigned int)train_id);
684 return;
685 }
686 g_pending_route_events[g_pending_route_event_count].train_id = train_id;
687 g_pending_route_events[g_pending_route_event_count].route_id = route_id;
688 g_pending_route_events[g_pending_route_event_count].start_signal = start_signal;
689 g_pending_route_events[g_pending_route_event_count].end_signal = end_signal;
690 g_pending_route_events[g_pending_route_event_count].route_status = route_status;
691 g_pending_route_events[g_pending_route_event_count].train_has_ma = train_has_ma;
692 g_pending_route_events[g_pending_route_event_count].fsm_state = fsm_state;
693 g_pending_route_events[g_pending_route_event_count].fsm_is_stub = route_fsm_state_is_stub(fsm_state);
694 g_pending_route_event_count++;
695}
696
700extern char *g_ga_role_tag;
701
711void ab_ga_il_on_route_context_changed(ab_gp_train_session_t *sessions, ab_gp_db_t *db,
712 const ab_gp_route_context_t *ctx, ab_gp_route_update_type_t update_type)
713{
714 const ab_site_ga_route_layout_t *layout = NULL;
715 uint32_t route_status;
716
717 if ((ctx == NULL) || (ctx->id == 0U))
718 {
719 return;
720 }
721 if ((update_type != AB_ROUTE_CONNECTED) && (update_type != AB_ROUTE_DISCONNECTED))
722 {
723 return; /* IL only cares about connect/disconnect - added/status-changed are ignored here */
724 }
725 route_status = (update_type == AB_ROUTE_CONNECTED) ? (uint32_t)RBC_ROUTE_STATUS_TRAIN_IN_ROUTE
726 : (uint32_t)RBC_ROUTE_STATUS_NO_TRAIN;
727 RTE_EXAMPLE_LOG("[%s] IL notified: route %u %s train %u\n", g_ga_role_tag, (unsigned int)ctx->id,
728 (update_type == AB_ROUTE_CONNECTED) ? "connected to" : "disconnected from",
729 (unsigned int)ctx->train_id);
730 if (!ab_ga_il_site_has_route(ctx->id, &layout))
731 {
732 return; /* shouldn't happen - ctx->id came from GP's own tracked route table */
733 }
734 enqueue_pending_route_event(ctx->train_id, ctx->id, layout->start_signal, layout->end_signal, route_status, (update_type == AB_ROUTE_CONNECTED), (uint8_t)ab_gp_db_route_get_fsm_state(db, ctx->id));
735
736 if ((sessions == NULL) || (db == NULL) || (ctx->train_id == 0U))
737 {
738 return; /* no train association to grant/shrink MA for (e.g. connected==false already handled above) */
739 }
740
741 /* Movement Authority grant/shrink - moved HERE from the old
742 * ROUTE_ADD/ROUTE_RELEASE-direct paths (this file's own
743 * handle_route_add()/handle_route_release()), on direct request -
744 * see this function's own header doc, ab_ga_il.h, for the full
745 * reasoning. Fires only once GP's own position-based connect gate
746 * has actually connected/disconnected this route to/from a train -
747 * ctx->train_id is GP's own real, tracked association, never
748 * anything IL supplied on the wire, and layout->length_m is this
749 * route's own known length, resolved by ctx->id - never a
750 * wire-supplied length either. */
751 if (update_type == AB_ROUTE_CONNECTED)
752 {
753 /* Remaining length from the train's own last-reported position
754 * to this route's own end signal - NOT the route's own full
755 * length unconditionally (a real bug, found live: a train
756 * connecting to a route already partway in was granted the
757 * whole route, telling it there is more track ahead than
758 * physically remains). Same correction as the real ERTMS
759 * engine's own ab_gp_proc_ma_compute()/_extend() - see
760 * ab_gp_proc_ma_remaining_length_m()'s own doc, ga_interface.h,
761 * for the fallback behavior when the train's position can't be
762 * resolved against this route at all. */
763 /* The MA is measured from the train's LRBG (Subset-026): total = distance(LRBG -> this route's end signal)
764 * minus the overlap, from chainage (ab_gp_proc_ma_remaining_length_m()). It is the WHOLE authority up to
765 * this route's end, not an amount to add on top of the routes already granted, so it replaces the granted
766 * length (the old one was relative to an earlier LRBG). If the train's position cannot be
767 * read at all, fall back to adding the route's authored length. */
768 ab_gp_train_context_t tctx_pos;
769
770 if (ab_gp_get_train_context(sessions, db, ctx->train_id, &tctx_pos))
771 {
772 uint32_t total_m = ab_gp_proc_ma_remaining_length_m(layout, tctx_pos.nid_lrbg, tctx_pos.d_lrbg);
773
774 total_m = (total_m > RTE_EXAMPLE_MA_OVERLAP_M) ? (total_m - RTE_EXAMPLE_MA_OVERLAP_M) : 0U;
775 (void)ab_gp_train_session_set_granted_length(sessions, ctx->train_id, total_m);
776 }
777 else
778 {
779 (void)ab_gp_train_session_add_granted_length(sessions, ctx->train_id, layout->length_m);
780 }
781 (void)ab_gp_train_session_increment_ma_seq(sessions, ctx->train_id);
782 /* ADR-034: this is the decision to grant a Movement Authority -
783 * see handle_route_add()'s own former identical call for why. */
784 AB_GA_CHECKPOINT_MARK();
785 (void)ab_gp_train_session_set_ma_pending_send(sessions, ctx->train_id, true);
786 (void)ab_gp_train_session_set_ma_acked(sessions, ctx->train_id, false);
787 (void)ab_ga_il_handle_route_add(db, ctx->train_id);
788 }
789 else /* AB_ROUTE_DISCONNECTED */
790 {
791 /* route_count already reflects this disconnect - GP's own
792 * ab_gp_db_route_disconnect_train() updates db->trains[].route_count/
793 * route_ids[] before firing this notification. */
794 ab_gp_train_context_t tctx;
795
796 (void)ab_gp_get_train_context(sessions, db, ctx->train_id, &tctx);
797 if (tctx.route_count == 0U)
798 {
799 rte_log_write_event(RTE_LOG_LEVEL_DEBUG, "-", 0U, "IL", g_ga_role_tag, "ROUTE_DISCONNECT_ZEROED_MA",
800 "no routes remain for this train - granted_length reset to 0", NULL);
801 (void)ab_gp_train_session_set_granted_length(sessions, ctx->train_id, 0U);
802 (void)ab_gp_train_session_set_ctc_ma_granted_sent(sessions, ctx->train_id, false);
803 }
804 else
805 {
806 /* The remaining authority ends at the last route the train still holds: re-derive it from the LRBG
807 * (distance to that route's end signal minus the overlap), never below what the train already
808 * passed and never above what it held. */
809 const ab_site_ga_route_layout_t *last = ab_site_ga_get_route(tctx.route_ids[tctx.route_count - 1U]);
810
811 if (last != NULL)
812 {
813 uint32_t total_m = ab_gp_proc_ma_remaining_length_m(last, tctx.nid_lrbg, tctx.d_lrbg);
814
815 total_m = (total_m > RTE_EXAMPLE_MA_OVERLAP_M) ? (total_m - RTE_EXAMPLE_MA_OVERLAP_M) : 0U;
816 if (total_m < tctx.granted_length)
817 {
818 rte_log_write_event(RTE_LOG_LEVEL_DEBUG, "-", 0U, "IL", g_ga_role_tag,
819 "ROUTE_DISCONNECT_SHRUNK_MA",
820 "granted_length re-derived from the last remaining route", NULL);
821 (void)ab_gp_train_session_set_granted_length(sessions, ctx->train_id, total_m);
822 }
823 }
824 }
825 }
826 /* Explicit trigger, on direct request - same "fire once, after every
827 * field is settled" rule this call has always followed - see
828 * ab_gp_train_notify_context_changed()'s own doc, ab_gp_train.h. */
829 ab_gp_train_notify_context_changed(sessions, db, ctx->train_id, AB_TRAIN_MA_CHANGED);
830}
bool ab_ga_il_route_remove(ab_gp_db_t *db, uint16_t route_id)
Removes a tracked route from GP's own route table, freeing its slot - thin wrapper over ga_interface....
Definition ab_ga_il.c:133
bool ab_ga_il_route_add(ab_gp_db_t *db, uint16_t route_id, uint16_t start_signal, uint16_t end_signal)
Adds a new route to GP's own route table, after verifying it via ab_ga_il_route_verify() above - thin...
Definition ab_ga_il.c:124
void ab_ga_il_on_route_context_changed(ab_gp_train_session_t *sessions, ab_gp_db_t *db, const ab_gp_route_context_t *ctx, ab_gp_route_update_type_t update_type)
GA's single dispatch module (ab_ga_dispatch.c) fans every route change here for AB_ROUTE_CONNECTED/AB...
Definition ab_ga_il.c:711
bool ab_ga_il_route_unassign_train(ab_gp_db_t *db, uint16_t route_id)
Reverses ab_ga_il_route_assign_train() - thin wrapper over ga_interface.h's own ab_gp_db_route_discon...
Definition ab_ga_il.c:153
static void il_wire_encode(uint8_t out[RBC_ENVELOPE_WIRE_SIZE], uint8_t train_id, uint32_t start_signal, uint32_t end_signal, uint32_t route_status)
Encodes a TRAIN_POSITION_IN_ROUTE envelope into raw wire bytes.
Definition ab_ga_il.c:359
bool ab_ga_il_route_verify(uint16_t route_id, uint16_t start_signal, uint16_t end_signal)
Verifies a route as reported by ILSim is OK: it must both exist in this site's own static layout AND ...
Definition ab_ga_il.c:113
static rbc_il_status_t g_il_status
Definition ab_ga_il.c:50
char * g_ga_role_tag
bool ab_ga_il_route_set_status(ab_gp_db_t *db, uint16_t route_id, ab_gp_db_route_il_status_t status)
Updates a tracked route's own il_status - IL's last-reported status for it - thin wrapper over ga_int...
Definition ab_ga_il.c:138
bool ab_ga_il_site_has_route_by_signals(uint16_t start_signal, uint16_t end_signal, const ab_site_ga_route_layout_t **out_layout)
Pure site-layout lookup by route IDENTITY - does a route with this exact (start_signal,...
Definition ab_ga_il.c:93
static bool handle_il_status(const ab_ga_il_status_msg_t *st, uint32_t cycle, const char *role_tag)
Processes a dedicated RBC_MSG_IL_STATUS (kind 26). Tracks IL health; a real hold/hold-off-trust react...
Definition ab_ga_il.c:641
static bool handle_il_route_cmd(ab_gp_db_t *db, const ab_ga_il_route_cmd_t *cmd, uint32_t cycle, rte_example_site_t site, const char *role_tag)
Processes a dedicated RBC_MSG_IL_ROUTE_CMD (kind 25).
Definition ab_ga_il.c:535
static void enqueue_pending_route_event(uint8_t train_id, uint16_t route_id, uint32_t start_signal, uint32_t end_signal, uint32_t route_status, bool train_has_ma, uint8_t fsm_state)
Appends a route notification event to the pending queue.
Definition ab_ga_il.c:661
static bool handle_route_add(ab_gp_db_t *db, const rbc_envelope_t *env, uint32_t cycle, rte_example_site_t site, const char *role_tag)
Processes an inbound ROUTE_ADD command from Interlocking.
Definition ab_ga_il.c:402
bool ab_ga_il_route_assign_train(ab_gp_db_t *db, uint16_t route_id, uint8_t train_id)
Assigns a route to a train - thin wrapper over ga_interface.h's own ab_gp_db_route_connect_train() (w...
Definition ab_ga_il.c:148
bool ab_ga_il_handle_route_add(ab_gp_db_t *db, uint8_t train_id)
Handles the MA-length-bookkeeping half of an inbound ROUTE_ADD - marks this train as now holding a va...
Definition ab_ga_il.c:158
uint8_t ab_ga_il_build_outputs(ab_gp_il_pending_output_t out[AB_GP_IL_MAX_PENDING_OUTPUTS])
Drains this module's own TrainPositionInRoute event queue (populated by this module's own route-conne...
Definition ab_ga_il.c:233
void ab_ga_il_confirm_outputs_sent(void)
Clears the pending-route-event queue ab_ga_il_build_outputs() just built from - call ONLY once those ...
Definition ab_ga_il.c:274
bool ab_ga_il_apply_envelope(const void *context, ab_gp_train_session_t *sessions, ab_gp_db_t *db, const uint8_t env_bytes[RBC_ENVELOPE_WIRE_SIZE], uint32_t cycle, rte_example_site_t site, const char *role_tag)
THE handler this module registers with GP (ab_gp_register_il_envelope_handler(), ga_interface....
Definition ab_ga_il.c:163
bool ab_ga_il_route_set_type(ab_gp_db_t *db, uint16_t route_id, uint32_t route_type)
Updates a tracked route's own route_type - IL's last-reported supervision mode (rbc_route_type_t: RBC...
Definition ab_ga_il.c:143
static bool handle_route_status_report(const rbc_envelope_t *env, uint32_t cycle, const char *role_tag)
Handles IL's own Phase D periodic route-status broadcast (RBC_MSG_ROUTE_STATUS_REPORT,...
Definition ab_ga_il.c:515
static bool il_wire_decode(const uint8_t in[RBC_ENVELOPE_WIRE_SIZE], rbc_envelope_t *out_env)
Decodes an RBC envelope frame into an rbc_envelope_t structure.
Definition ab_ga_il.c:289
bool ab_ga_il_site_has_route(uint16_t route_id, const ab_site_ga_route_layout_t **out_layout)
Pure site-layout lookup - does route_id exist in this site's own static track layout at all (ab_site_...
Definition ab_ga_il.c:71
static bool handle_route_release(ab_gp_db_t *db, const rbc_envelope_t *env, uint32_t cycle, rte_example_site_t site, const char *role_tag)
Processes an inbound ROUTE_RELEASE command from Interlocking.
Definition ab_ga_il.c:444
A/B's own route handling against GP's own runtime table (via AB/GP/ga_interface/ga_interface....
bool ab_ga_il_wire_decode_route_cmd(const uint8_t frame[RBC_ENVELOPE_WIRE_SIZE], ab_ga_il_route_cmd_t *out)
Decodes + fully range-validates an RBC_MSG_IL_ROUTE_CMD frame.
void ab_ga_il_wire_encode_route_indication(uint8_t frame[RBC_ENVELOPE_WIRE_SIZE], const ab_ga_il_route_indication_t *in)
Encodes an RBC_MSG_IL_ROUTE_INDICATION frame (zero-fills the whole RBC_ENVELOPE_WIRE_SIZE slot first)...
bool ab_ga_il_wire_decode_status(const uint8_t frame[RBC_ENVELOPE_WIRE_SIZE], ab_ga_il_status_msg_t *out)
Decodes + validates an RBC_MSG_IL_STATUS frame. Same contract.
Dedicated IL <-> RBC message payloads and codec (wire kinds RBC_MSG_IL_ROUTE_CMD / RBC_MSG_IL_STATUS ...
ab_ga_route_fsm_result_t ab_ga_route_fsm_step(ab_gp_db_t *db, uint16_t route_id, const ab_ga_il_route_cmd_t *cmd, uint32_t cycle, const char *role_tag)
Applies cmd to the route's FSM: reads the current state via ab_gp_db_route_get_fsm_state(),...
Per-route state machine, driven by IL route commands (RBC_MSG_IL_ROUTE_CMD / ab_ga_il_route_cmd_t).
@ AB_GA_ROUTE_STATE_DEGRADED
@ AB_GA_ROUTE_STATE_NO_ROUTE
@ AB_GA_ROUTE_STATE_OS
@ AB_GA_ROUTE_STATE_SH
const ab_site_ga_route_layout_t ab_site_ga_track_layout[]
const ab_site_ga_route_layout_t * ab_site_ga_get_route(uint16_t route_id)
Looks up a route by its ID.
Decoded RBC_MSG_IL_ROUTE_CMD payload.
RBC_MSG_IL_ROUTE_INDICATION payload (RBC -> IL).
rbc_il_emergency_type_t emergency_type
Decoded RBC_MSG_IL_STATUS payload.
Result of applying one IL route command to a route's FSM.
ab_ga_route_state_t to
One route's static layout entry (railML interlocking <route>) - GA's own full definition (not a reduc...
Definition ab_siteGA.h:69
Queued route event for IL notification dispatch.
Definition ab_ga_il.c:27