SafeAPI RBC 2oo2 Generic Application
IL/CTC route and train logic for the safeAPIRBC2oo2 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 "safeapi/oal/memory/sapi_mem_util.h"
11#include "safeapi/utils/buffer/sapi_buffer.h"
12#include "safeapi/utils/cast/sapi_cast.h"
13#include "safeapi/oal/log/sapi_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, sapi_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, sapi_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 sapi_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 sapi_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 sapi_log_write_event(SAPI_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 sapi_log_write_event(SAPI_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 SAFEAPI_EXAMPLE_LOG("[%s] cycle %u: malformed or unexpected-kind envelope from C (IL relay)\n", role_tag,
212 cycle);
213 sapi_log_write_event(SAPI_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 SAFEAPI_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_IN_ROUTE");
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 g_pending_route_event_count = 0U;
272 return oi;
273}
274
275/* ab_ga_il_init() REMOVED - its entire body was two GP registrations,
276 * both now owned by ab_ga_dispatch.c's own single subscription (see this
277 * file's own module doc and ab_ga_dispatch.h). */
278
285static bool il_wire_decode(const uint8_t in[RBC_ENVELOPE_WIRE_SIZE], rbc_envelope_t *out_env)
286{
287 sapi_buffer_t buf;
288 rbc_envelope_t decoded;
289 uint16_t l_message;
290 const uint16_t expected_l_message =
291 (uint16_t)(IL_WIRE_HEADER_SIZE + IL_WIRE_CONTENT_SIZE_ROUTE_IDENTITY);
292 bool is_route_add;
293 bool is_route_status_report;
294
295 is_route_add = (in[0] == (uint8_t)RBC_MSG_ROUTE_ADD);
296 is_route_status_report = (in[0] == (uint8_t)RBC_MSG_ROUTE_STATUS_REPORT);
297 if (!is_route_add && !is_route_status_report && (in[0] != (uint8_t)RBC_MSG_ROUTE_RELEASE))
298 {
299 return false;
300 }
301 sapi_mem_set(&decoded, 0, sizeof(decoded));
302 decoded.kind = (rbc_msg_kind_t)in[0];
303
304 if (sapi_buffer_init(&buf, (uint8_t *)in, RBC_ENVELOPE_WIRE_SIZE) != SAPI_STATUS_OK)
305 {
306 return false;
307 }
308 (void)sapi_buffer_set_length(&buf, RBC_ENVELOPE_WIRE_SIZE);
309
310 if (sapi_buffer_read_u16_le(&buf, 4U, &l_message) != SAPI_STATUS_OK)
311 {
312 return false;
313 }
314 if (l_message != expected_l_message)
315 {
316 return false;
317 }
318 decoded.l_message = l_message;
319 decoded.nid_message = in[2];
320 decoded.train_id = in[1];
321
322 if ((sapi_buffer_read_u32_le(&buf, 8U, &decoded.t_train) != SAPI_STATUS_OK)
323 || (sapi_buffer_read_u32_le(&buf, 80U, &decoded.start_signal) != SAPI_STATUS_OK)
324 || (sapi_buffer_read_u32_le(&buf, 84U, &decoded.end_signal) != SAPI_STATUS_OK))
325 {
326 return false;
327 }
328 if (is_route_status_report)
329 {
330 /* Same offset TRAIN_POSITION_IN_ROUTE's own il_wire_encode() writes
331 * route_status at (92U, after start_signal@80/end_signal@84 and a
332 * zero-padding u32 at 88 where ROUTE_ADD's route_type lives instead) -
333 * this is IL originating the SAME wire shape in the OTHER direction. */
334 if (sapi_buffer_read_u32_le(&buf, 92U, &decoded.route_status) != SAPI_STATUS_OK)
335 {
336 return false;
337 }
338 }
339 if (is_route_add)
340 {
341 if ((sapi_buffer_read_u32_le(&buf, 24U, &decoded.route_len) != SAPI_STATUS_OK)
342 || (sapi_buffer_read_u32_le(&buf, 88U, &decoded.route_type) != SAPI_STATUS_OK))
343 {
344 return false;
345 }
346 }
347
348 *out_env = decoded;
349 return true;
350}
351
355static void il_wire_encode(uint8_t out[RBC_ENVELOPE_WIRE_SIZE], uint8_t train_id, uint32_t start_signal,
356 uint32_t end_signal, uint32_t route_status)
357{
358 sapi_buffer_t buf;
359 uint16_t l_message = (uint16_t)(IL_WIRE_HEADER_SIZE + IL_WIRE_CONTENT_SIZE_ROUTE_IDENTITY);
360
361 sapi_mem_set(out, 0, RBC_ENVELOPE_WIRE_SIZE);
362 (void)sapi_buffer_init(&buf, out, RBC_ENVELOPE_WIRE_SIZE);
363 out[0] = (uint8_t)RBC_MSG_TRAIN_POSITION_IN_ROUTE;
364 out[1] = train_id;
365 out[2] = 0U;
366 out[3] = 0U;
367 (void)sapi_buffer_set_length(&buf, 4U);
368 (void)sapi_buffer_write_u16_le(&buf, l_message);
369 (void)sapi_buffer_write_u16_le(&buf, 0U);
370 (void)sapi_buffer_write_u32_le(&buf, 0U);
371 (void)sapi_buffer_set_length(&buf, 80U);
372 (void)sapi_buffer_write_u32_le(&buf, start_signal);
373 (void)sapi_buffer_write_u32_le(&buf, end_signal);
374 (void)sapi_buffer_write_u32_le(&buf, 0U);
375 (void)sapi_buffer_write_u32_le(&buf, route_status);
376}
377
398static bool handle_route_add(ab_gp_db_t *db, const rbc_envelope_t *env, uint32_t cycle, sapi_example_site_t site,
399 const char *role_tag)
400{
401 const ab_site_ga_route_layout_t *layout = NULL;
402
403 if (!ab_ga_il_site_has_route_by_signals((uint16_t)env->start_signal, (uint16_t)env->end_signal, &layout))
404 {
405 SAFEAPI_EXAMPLE_LOG(
406 "[%s] cycle %u: ROUTE_ADD for unknown route (start_signal=%u end_signal=%u) - dropping\n", role_tag,
407 cycle, (unsigned int)env->start_signal, (unsigned int)env->end_signal);
408 sapi_log_write_event(SAPI_LOG_LEVEL_ERROR, site_config_name(site), cycle, "IL", role_tag, "ROUTE_ADD",
409 "unknown route", NULL);
410 return false;
411 }
412 if (!ab_gp_db_route_exists(db, layout->id))
413 {
414 (void)ab_ga_il_route_add(db, layout->id, layout->start_signal, layout->end_signal);
415 }
416 (void)ab_ga_il_route_set_type(db, layout->id, env->route_type);
417
418 SAFEAPI_EXAMPLE_LOG("[%s] cycle %u: [ONLINE] route %u tracked (type=%u) - MA grant awaits GP's own "
419 "position-based connect\n",
420 role_tag, cycle, (unsigned int)layout->id, (unsigned int)env->route_type);
421 sapi_log_write_event(SAPI_LOG_LEVEL_INFO, site_config_name(site), cycle, "IL", role_tag, "ROUTE_ADD", "tracked",
422 NULL);
423 return true;
424}
425
440static bool handle_route_release(ab_gp_db_t *db, const rbc_envelope_t *env, uint32_t cycle,
441 sapi_example_site_t site, const char *role_tag)
442{
443 const ab_site_ga_route_layout_t *layout = NULL;
444 ab_gp_route_context_t rctx;
445
446 if (!ab_ga_il_site_has_route_by_signals((uint16_t)env->start_signal, (uint16_t)env->end_signal, &layout))
447 {
448 SAFEAPI_EXAMPLE_LOG(
449 "[%s] cycle %u: ROUTE_RELEASE for unknown route (start_signal=%u end_signal=%u) - dropping\n", role_tag,
450 cycle, (unsigned int)env->start_signal, (unsigned int)env->end_signal);
451 sapi_log_write_event(SAPI_LOG_LEVEL_ERROR, site_config_name(site), cycle, "IL", role_tag, "ROUTE_RELEASE",
452 "unknown route", NULL);
453 return false;
454 }
455 if (!ab_gp_db_route_exists(db, layout->id))
456 {
457 SAFEAPI_EXAMPLE_LOG("[%s] cycle %u: ROUTE_RELEASE for route %u - not tracked, acknowledging noTrain\n", role_tag,
458 cycle, (unsigned int)layout->id);
459 /* No train is genuinely known for an untracked route - 0, not
460 * env->train_id (see this function's own doc). */
461 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);
462 return true;
463 }
464 /* used_in_ma read now via ab_gp_db_get_route_context() - the
465 * individual _get_used_in_ma() getter this used to call is gone
466 * (later session, on direct request). */
467 (void)ab_gp_db_get_route_context(db, layout->id, &rctx);
468 if (rctx.used_in_ma)
469 {
470 /* Fires AB_ROUTE_DISCONNECTED synchronously (GP's own
471 * ab_gp_db_route_disconnect_train(), notify_route_context()) -
472 * ab_ga_il_on_route_context_changed() does the MA shrink/zero AND
473 * its own enqueue_pending_route_event(), using the route's own
474 * real (pre-clear) train_id - nothing further needed here. */
475 (void)ab_ga_il_route_unassign_train(db, layout->id);
476 }
477 else
478 {
479 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);
480 }
481 (void)ab_ga_il_route_remove(db, layout->id);
482 /* Distinct from "released" below (train association dropped): this
483 * marks the DB entry itself gone (ab_ga_il_route_remove(), just
484 * above) - on direct request, "removing" is its own logged fact,
485 * not folded silently into the release line. */
486 SAFEAPI_EXAMPLE_LOG("[%s] cycle %u: route %u removed from DB\n", role_tag, cycle, (unsigned int)layout->id);
487 sapi_log_write_event(SAPI_LOG_LEVEL_INFO, site_config_name(site), cycle, "IL", role_tag, "ROUTE_REMOVED",
488 "removed", NULL);
489
490 SAFEAPI_EXAMPLE_LOG("[%s] cycle %u: route %u released\n", role_tag, cycle, (unsigned int)layout->id);
491 sapi_log_write_event(SAPI_LOG_LEVEL_INFO, site_config_name(site), cycle, "IL", role_tag, "ROUTE_RELEASE",
492 "released", NULL);
493 return true;
494}
495
511static bool handle_route_status_report(const rbc_envelope_t *env, uint32_t cycle, const char *role_tag)
512{
513 bool known = ab_ga_il_site_has_route_by_signals((uint16_t)env->start_signal, (uint16_t)env->end_signal, NULL);
514
515 SAFEAPI_EXAMPLE_LOG(
516 "[%s] cycle %u: ROUTE_STATUS_REPORT start_signal=%u end_signal=%u route_status=%u (route %s)\n", role_tag,
517 cycle, (unsigned int)env->start_signal, (unsigned int)env->end_signal, (unsigned int)env->route_status,
518 known ? "known" : "UNKNOWN to this site's own layout");
519 return true;
520}
521
531static bool handle_il_route_cmd(ab_gp_db_t *db, const ab_ga_il_route_cmd_t *cmd, uint32_t cycle,
532 sapi_example_site_t site, const char *role_tag)
533{
534 uint16_t route_id;
535 bool want_route;
537
538 /* Need a concrete route identity to track/step. int32 -1 (or an
539 * out-of-uint16 value) => no specific route => accepted but no-op. */
540 if ((cmd->route_id < 0) || (cmd->route_id > (int32_t)UINT16_MAX))
541 {
542 SAPI_LOG_WRITE_EVENT_F(SAPI_LOG_LEVEL_WARNING, site_config_name(site), cycle, "IL", role_tag,
543 "IL_ROUTE_CMD",
544 "no route_id (=%d) - command ignored (route_type=%d route_status=%d release=%d)",
545 (int)cmd->route_id, (int)cmd->route_type, (int)cmd->route_status,
546 cmd->release_route_request ? 1 : 0);
547 return true;
548 }
549 route_id = (uint16_t)cmd->route_id;
550
551 want_route = (!cmd->release_route_request) && (cmd->route_type == RBC_IL_ROUTE_TYPE_LOCKED);
552
553 if (want_route)
554 {
555 /* Track the route if the site knows it and it is not tracked yet.
556 * start/end signals are optional in the command (-1); when both
557 * are given, verify + add through the same site-checked path
558 * ROUTE_ADD uses; otherwise, if the route is already tracked,
559 * leave it. */
560 if (!ab_gp_db_route_exists(db, route_id))
561 {
562 const ab_site_ga_route_layout_t *layout = NULL;
563
564 if ((cmd->start_signal >= 0) && (cmd->end_signal >= 0)
566 (uint16_t)cmd->end_signal, &layout)
567 && (layout->id == route_id))
568 {
569 (void)ab_ga_il_route_add(db, route_id, layout->start_signal, layout->end_signal);
570 }
571 else if (ab_ga_il_site_has_route(route_id, &layout))
572 {
573 (void)ab_ga_il_route_add(db, route_id, layout->start_signal, layout->end_signal);
574 }
575 else
576 {
577 SAPI_LOG_WRITE_EVENT_F(SAPI_LOG_LEVEL_ERROR, site_config_name(site), cycle, "IL", role_tag,
578 "IL_ROUTE_CMD",
579 "route %u (start_signal=%d end_signal=%d) unknown to this site's layout - rejecting",
580 (unsigned int)route_id, (int)cmd->start_signal, (int)cmd->end_signal);
581 return false;
582 }
583 }
584 }
585
586 fsm = ab_ga_route_fsm_step(db, route_id, cmd, cycle, role_tag);
587
588 if ((!want_route) && ab_gp_db_route_exists(db, route_id))
589 {
590 /* Release / NO_ROUTE: untrack. Any connected train is
591 * disconnected through GP's own path (fires
592 * ab_ga_il_on_route_context_changed()'s AB_ROUTE_DISCONNECTED,
593 * which does the MA shrink AND its own indication) - same as
594 * handle_route_release(). */
595 (void)ab_ga_il_route_remove(db, route_id);
596 }
597
598 /* Acknowledge EVERY route command with a fresh RBC_MSG_IL_ROUTE_INDICATION
599 * reflecting the RBC's resulting per-route view - so IL gets feedback
600 * even for a command that connects no train (the connect/disconnect
601 * hook only fires when a train is actually involved). Route identity
602 * always comes from the static site layout, never the (optional)
603 * wire fields. */
604 {
605 const ab_site_ga_route_layout_t *layout = NULL;
606
607 if (ab_ga_il_site_has_route(route_id, &layout))
608 {
609 uint8_t alloc_train = 0U;
610 bool has_ma = false;
611 uint32_t rstatus;
612
613 if (ab_gp_db_route_exists(db, route_id))
614 {
615 ab_gp_route_context_t rctx;
616 if (ab_gp_db_get_route_context(db, route_id, &rctx))
617 {
618 alloc_train = rctx.train_id;
619 has_ma = rctx.used_in_ma;
620 }
621 }
622 rstatus = (alloc_train != 0U) ? (uint32_t)RBC_ROUTE_STATUS_TRAIN_IN_ROUTE
623 : (uint32_t)RBC_ROUTE_STATUS_NO_TRAIN;
624 enqueue_pending_route_event(alloc_train, route_id, layout->start_signal,
625 layout->end_signal, rstatus, has_ma, (uint8_t)fsm.to);
626 }
627 }
628
629 return true;
630}
631
637static bool handle_il_status(const ab_ga_il_status_msg_t *st, uint32_t cycle, const char *role_tag)
638{
639 g_il_status = st->il_status;
640
641 if ((st->il_status == RBC_IL_STATUS_DOWN) || (st->il_status == RBC_IL_STATUS_RESTARTING))
642 {
643 SAFEAPI_EXAMPLE_LOG("[%s] cycle %u: IL_STATUS=%s - route state trust HOLD not implemented yet (stub)\n",
644 role_tag, cycle,
645 (st->il_status == RBC_IL_STATUS_DOWN) ? "DOWN" : "RESTARTING");
646 }
647 else
648 {
649 SAFEAPI_EXAMPLE_LOG("[%s] cycle %u: IL_STATUS=UP\n", role_tag, cycle);
650 }
651 return true;
652}
653
657static void enqueue_pending_route_event(uint8_t train_id, uint16_t route_id, uint32_t start_signal,
658 uint32_t end_signal, uint32_t route_status, bool train_has_ma,
659 uint8_t fsm_state)
660{
661 uint8_t i;
662 /* Each queued event yields TWO output frames (legacy + indication),
663 * so the effective ceiling is half the array. */
664 const uint8_t max_events = (uint8_t)(AB_GP_IL_MAX_PENDING_OUTPUTS / 2U);
665
666 for (i = 0U; i < g_pending_route_event_count; i++)
667 {
668 if ((g_pending_route_events[i].route_id == route_id) &&
669 (g_pending_route_events[i].train_id == train_id) &&
670 (g_pending_route_events[i].route_status == route_status) &&
671 (g_pending_route_events[i].fsm_state == fsm_state))
672 {
673 return; /* already queued this exact route view this cycle */
674 }
675 }
676 if (g_pending_route_event_count >= max_events)
677 {
678 SAFEAPI_EXAMPLE_LOG("IL: pending route-indication queue full - dropping event for train %u\n",
679 (unsigned int)train_id);
680 return;
681 }
682 g_pending_route_events[g_pending_route_event_count].train_id = train_id;
683 g_pending_route_events[g_pending_route_event_count].route_id = route_id;
684 g_pending_route_events[g_pending_route_event_count].start_signal = start_signal;
685 g_pending_route_events[g_pending_route_event_count].end_signal = end_signal;
686 g_pending_route_events[g_pending_route_event_count].route_status = route_status;
687 g_pending_route_events[g_pending_route_event_count].train_has_ma = train_has_ma;
688 g_pending_route_events[g_pending_route_event_count].fsm_state = fsm_state;
689 g_pending_route_events[g_pending_route_event_count].fsm_is_stub = route_fsm_state_is_stub(fsm_state);
690 g_pending_route_event_count++;
691}
692
696extern char *g_ga_role_tag;
697
707void ab_ga_il_on_route_context_changed(ab_gp_train_session_t *sessions, ab_gp_db_t *db,
708 const ab_gp_route_context_t *ctx, ab_gp_route_update_type_t update_type)
709{
710 const ab_site_ga_route_layout_t *layout = NULL;
711 uint32_t route_status;
712
713 if ((update_type != AB_ROUTE_CONNECTED) && (update_type != AB_ROUTE_DISCONNECTED))
714 {
715 return; /* IL only cares about connect/disconnect - added/status-changed are ignored here */
716 }
717 route_status = (update_type == AB_ROUTE_CONNECTED) ? (uint32_t)RBC_ROUTE_STATUS_TRAIN_IN_ROUTE
718 : (uint32_t)RBC_ROUTE_STATUS_NO_TRAIN;
719 SAFEAPI_EXAMPLE_LOG("[%s] IL notified: route %u %s train %u\n", g_ga_role_tag, (unsigned int)ctx->id,
720 (update_type == AB_ROUTE_CONNECTED) ? "connected to" : "disconnected from",
721 (unsigned int)ctx->train_id);
722 if (!ab_ga_il_site_has_route(ctx->id, &layout))
723 {
724 return; /* shouldn't happen - ctx->id came from GP's own tracked route table */
725 }
726 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));
727
728 if ((sessions == NULL) || (db == NULL) || (ctx->train_id == 0U))
729 {
730 return; /* no train association to grant/shrink MA for (e.g. connected==false already handled above) */
731 }
732
733 /* Movement Authority grant/shrink - moved HERE from the old
734 * ROUTE_ADD/ROUTE_RELEASE-direct paths (this file's own
735 * handle_route_add()/handle_route_release()), on direct request -
736 * see this function's own header doc, ab_ga_il.h, for the full
737 * reasoning. Fires only once GP's own position-based connect gate
738 * has actually connected/disconnected this route to/from a train -
739 * ctx->train_id is GP's own real, tracked association, never
740 * anything IL supplied on the wire, and layout->length_m is this
741 * route's own known length, resolved by ctx->id - never a
742 * wire-supplied length either. */
743 if (update_type == AB_ROUTE_CONNECTED)
744 {
745 /* Remaining length from the train's own last-reported position
746 * to this route's own end signal - NOT the route's own full
747 * length unconditionally (a real bug, found live: a train
748 * connecting to a route already partway in was granted the
749 * whole route, telling it there is more track ahead than
750 * physically remains). Same correction as the real ERTMS
751 * engine's own ab_gp_proc_ma_compute()/_extend() - see
752 * ab_gp_proc_ma_remaining_length_m()'s own doc, ga_interface.h,
753 * for the fallback behavior when the train's position can't be
754 * resolved against this route at all. */
755 ab_gp_train_context_t tctx_pos;
756 uint32_t remaining_m = layout->length_m;
757 bool is_first_route = true;
758
759 if (ab_gp_get_train_context(sessions, db, ctx->train_id, &tctx_pos))
760 {
761 remaining_m = ab_gp_proc_ma_remaining_length_m(layout, tctx_pos.nid_lrbg, tctx_pos.d_lrbg);
762 is_first_route = (tctx_pos.granted_length == 0U);
763 }
764 /* End/Limit of Authority sits one overlap margin short of the last
765 * route's end signal (SAFEAPI_EXAMPLE_MA_OVERLAP_M, common_config.h) -
766 * subtracted once, from the FIRST route's contribution; each later
767 * ROUTE_CONNECT adds its own full remaining length, so the single
768 * margin carries to whatever the new last end signal is. Same
769 * intent as ab_gp_proc_ma_compute()'s on the real-ERTMS path. */
770 if (is_first_route)
771 {
772 remaining_m = (remaining_m > SAFEAPI_EXAMPLE_MA_OVERLAP_M)
773 ? (remaining_m - SAFEAPI_EXAMPLE_MA_OVERLAP_M) : 0U;
774 }
775 (void)ab_gp_train_session_add_granted_length(sessions, ctx->train_id, remaining_m);
776 (void)ab_gp_train_session_increment_ma_seq(sessions, ctx->train_id);
777 /* ADR-034: this is the decision to grant a Movement Authority -
778 * see handle_route_add()'s own former identical call for why. */
779 AB_GA_CHECKPOINT_MARK();
780 (void)ab_gp_train_session_set_ma_pending_send(sessions, ctx->train_id, true);
781 (void)ab_gp_train_session_set_ma_acked(sessions, ctx->train_id, false);
782 (void)ab_ga_il_handle_route_add(db, ctx->train_id);
783 }
784 else /* AB_ROUTE_DISCONNECTED */
785 {
786 /* route_count already reflects this disconnect - GP's own
787 * ab_gp_db_route_disconnect_train() updates db->trains[].route_count/
788 * route_ids[] before firing this notification. */
789 ab_gp_train_context_t tctx;
790
791 (void)ab_gp_get_train_context(sessions, db, ctx->train_id, &tctx);
792 if (tctx.route_count == 0U)
793 {
794 sapi_log_write_event(SAPI_LOG_LEVEL_DEBUG, "-", 0U, "IL", g_ga_role_tag, "ROUTE_DISCONNECT_ZEROED_MA",
795 "no routes remain for this train - granted_length reset to 0", NULL);
796 (void)ab_gp_train_session_set_granted_length(sessions, ctx->train_id, 0U);
797 (void)ab_gp_train_session_set_ctc_ma_granted_sent(sessions, ctx->train_id, false);
798 }
799 else if (tctx.granted_length >= layout->length_m)
800 {
801 sapi_log_write_event(SAPI_LOG_LEVEL_DEBUG, "-", 0U, "IL", g_ga_role_tag, "ROUTE_DISCONNECT_SHRUNK_MA",
802 "granted_length reduced by this route's own length", NULL);
803 (void)ab_gp_train_session_set_granted_length(sessions, ctx->train_id, tctx.granted_length - layout->length_m);
804 }
805 }
806 /* Explicit trigger, on direct request - same "fire once, after every
807 * field is settled" rule this call has always followed - see
808 * ab_gp_train_notify_context_changed()'s own doc, ab_gp_train.h. */
809 ab_gp_train_notify_context_changed(sessions, db, ctx->train_id, AB_TRAIN_MA_CHANGED);
810}
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:707
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, sapi_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_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:355
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:637
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:657
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
static bool handle_route_release(ab_gp_db_t *db, const rbc_envelope_t *env, uint32_t cycle, sapi_example_site_t site, const char *role_tag)
Processes an inbound ROUTE_RELEASE command from Interlocking.
Definition ab_ga_il.c:440
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
static bool handle_il_route_cmd(ab_gp_db_t *db, const ab_ga_il_route_cmd_t *cmd, uint32_t cycle, sapi_example_site_t site, const char *role_tag)
Processes a dedicated RBC_MSG_IL_ROUTE_CMD (kind 25).
Definition ab_ga_il.c:531
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
static bool handle_route_add(ab_gp_db_t *db, const rbc_envelope_t *env, uint32_t cycle, sapi_example_site_t site, const char *role_tag)
Processes an inbound ROUTE_ADD command from Interlocking.
Definition ab_ga_il.c:398
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:511
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:285
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
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[]
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