25static uint32_t crc32(
const uint8_t *data, uint32_t len)
27 uint32_t crc = 0xffffffff;
30 if (data == NULL)
return 0;
32 for (i = 0; i < len; i++) {
33 uint8_t
byte = data[i];
34 crc = (crc >> 8) ^ (uint32_t)(crc ^ byte);
37 return crc ^ 0xffffffff;
67 if (site == NULL || cmd_a == NULL || cmd_b == NULL) {
71 site->stats.total_votes++;
74 uint32_t checksum_a = crc32((
const uint8_t *)cmd_a,
75 sizeof(*cmd_a) -
sizeof(cmd_a->checksum));
76 uint32_t checksum_b = crc32((
const uint8_t *)cmd_b,
77 sizeof(*cmd_b) -
sizeof(cmd_b->checksum));
79 if (checksum_a != cmd_a->checksum) {
80 site->stats.channel_a_errors++;
81 snprintf(site->channels.service_output,
sizeof(site->channels.service_output),
82 "[SITE %s] Channel A corruption (CRC fail), using safe state",
83 site->site_id == SITE_WEST ?
"WEST" :
"EAST");
84 site->channels.voting_result = 2;
85 site->channels.voted_command = safe_command(cmd_a->train_id);
89 if (checksum_b != cmd_b->checksum) {
90 site->stats.channel_b_errors++;
91 snprintf(site->channels.service_output,
sizeof(site->channels.service_output),
92 "[SITE %s] Channel B corruption (CRC fail), using safe state",
93 site->site_id == SITE_WEST ?
"WEST" :
"EAST");
94 site->channels.voting_result = 2;
95 site->channels.voted_command = safe_command(cmd_b->train_id);
100 if (cmd_a->train_id != cmd_b->train_id ||
101 cmd_a->position != cmd_b->position ||
102 cmd_a->signal_state != cmd_b->signal_state ||
103 cmd_a->speed_limit != cmd_b->speed_limit) {
106 site->stats.total_mismatches++;
107 site->channels.voting_result = 1;
108 site->channels.voted_command = safe_command(cmd_a->train_id);
110 snprintf(site->channels.service_output,
sizeof(site->channels.service_output),
111 "[SITE %s] MISMATCH: A(train=%u,sig=%u,spd=%u) vs "
112 "B(train=%u,sig=%u,spd=%u) → SAFE_STATE",
113 site->site_id == SITE_WEST ?
"WEST" :
"EAST",
114 cmd_a->train_id, cmd_a->signal_state, cmd_a->speed_limit,
115 cmd_b->train_id, cmd_b->signal_state, cmd_b->speed_limit);
120 site->stats.total_matches++;
121 site->channels.voting_result = 0;
122 site->channels.voted_command = *cmd_a;
123 site->channels.channel_a = *cmd_a;
124 site->channels.channel_b = *cmd_b;
126 snprintf(site->channels.service_output,
sizeof(site->channels.service_output),
127 "[SITE %s] MATCH: train=%u, pos=%u, sig=%u, speed=%u km/h",
128 site->site_id == SITE_WEST ?
"WEST" :
"EAST",
129 cmd_a->train_id, cmd_a->position,
130 cmd_a->signal_state, cmd_a->speed_limit);
141 if (voter == NULL || config == NULL) {
145 memset(voter, 0,
sizeof(*voter));
146 voter->config = *config;
149 voter->west.site_id = SITE_WEST;
150 voter->west.state = SITE_STATE_ONLINE;
151 voter->west.heartbeat_timestamp_ms = (uint32_t)(time(NULL) * 1000);
154 voter->east.site_id = SITE_EAST;
155 voter->east.state = SITE_STATE_STANDBY;
156 voter->east.heartbeat_timestamp_ms = (uint32_t)(time(NULL) * 1000);
159 voter->active_site = SITE_WEST;
162 "[MULTI-SITE] Initialized: %s (ONLINE) vs %s (STANDBY)\n",
163 config->west_site_name,
164 config->east_site_name);
178 if (voter == NULL || cmd_a == NULL || cmd_b == NULL) {
182 site_context_t *site_ctx = (site == SITE_WEST) ? &voter->west : &voter->east;
185 site_ctx->stats.sequence_number++;
188 return site_vote(site_ctx, cmd_a, cmd_b);
198 return voter->active_site;
201 uint32_t now = (uint32_t)(time(NULL) * 1000);
205 (voter->active_site == SITE_WEST) ? &voter->west : &voter->east;
207 (voter->active_site == SITE_WEST) ? &voter->east : &voter->west;
209 uint32_t time_since_active = now - active_site_ctx->heartbeat_timestamp_ms;
211 if (time_since_active > voter->config.heartbeat_timeout_ms) {
212 active_site_ctx->heartbeat_miss_count++;
214 if (active_site_ctx->heartbeat_miss_count >=
215 voter->config.max_heartbeat_misses) {
219 "\n[MULTI-SITE FAILOVER] %s lost heartbeat (%u ms), "
220 "switching to %s\n\n",
221 active_site_ctx->site_id == SITE_WEST ?
"WEST" :
"EAST",
223 standby_site_ctx->site_id == SITE_WEST ?
"WEST" :
"EAST");
226 active_site_ctx->state = SITE_STATE_FAULTY;
229 standby_site_ctx->state = SITE_STATE_ONLINE;
231 (voter->active_site == SITE_WEST) ? SITE_EAST : SITE_WEST;
234 standby_site_ctx->heartbeat_timestamp_ms = now;
235 standby_site_ctx->heartbeat_miss_count = 0;
237 return voter->active_site;
241 active_site_ctx->heartbeat_miss_count = 0;
245 active_site_ctx->heartbeat_timestamp_ms = now;
247 return voter->active_site;
255 site_id_t new_active)
261 if (new_active == voter->active_site) {
266 (voter->active_site == SITE_WEST) ? &voter->west : &voter->east;
267 site_context_t *new_active_ctx = (new_active == SITE_WEST) ? &voter->west : &voter->east;
270 "[MULTI-SITE FAILOVER] Manual request: switching from %s to %s\n",
271 old_active->site_id == SITE_WEST ?
"WEST" :
"EAST",
272 new_active == SITE_WEST ?
"WEST" :
"EAST");
274 old_active->state = SITE_STATE_STANDBY;
275 new_active_ctx->state = SITE_STATE_ONLINE;
276 voter->active_site = new_active;
288 if (voter == NULL || cmd_out == NULL) {
293 &voter->west : &voter->east;
295 *cmd_out = active->channels.voted_command;
307 if (voter == NULL || output_buf == NULL || buf_size == 0) {
312 &voter->west : &voter->east;
314 strncpy(output_buf, active->channels.service_output, buf_size - 1);
315 output_buf[buf_size - 1] =
'\0';
317 return (
int)strlen(output_buf);
331 printf(
"═══════════════════════════════════════════════════════════════\n");
332 printf(
"Multi-Site Voting Statistics\n");
333 printf(
"═══════════════════════════════════════════════════════════════\n");
335 printf(
"\nWEST Site (%s):\n", voter->west.state == SITE_STATE_ONLINE ?
"ONLINE" :
336 voter->west.state == SITE_STATE_STANDBY ?
"STANDBY" :
"FAULTY");
337 printf(
" Total votes: %u\n", voter->west.stats.total_votes);
338 printf(
" Matches: %u (%.1f%%)\n", voter->west.stats.total_matches,
339 voter->west.stats.total_votes > 0 ?
340 (100.0 * voter->west.stats.total_matches / voter->west.stats.total_votes) : 0.0);
341 printf(
" Mismatches: %u\n", voter->west.stats.total_mismatches);
342 printf(
" Ch-A errors: %u\n", voter->west.stats.channel_a_errors);
343 printf(
" Ch-B errors: %u\n", voter->west.stats.channel_b_errors);
345 printf(
"\nEAST Site (%s):\n", voter->east.state == SITE_STATE_ONLINE ?
"ONLINE" :
346 voter->east.state == SITE_STATE_STANDBY ?
"STANDBY" :
"FAULTY");
347 printf(
" Total votes: %u\n", voter->east.stats.total_votes);
348 printf(
" Matches: %u (%.1f%%)\n", voter->east.stats.total_matches,
349 voter->east.stats.total_votes > 0 ?
350 (100.0 * voter->east.stats.total_matches / voter->east.stats.total_votes) : 0.0);
351 printf(
" Mismatches: %u\n", voter->east.stats.total_mismatches);
352 printf(
" Ch-A errors: %u\n", voter->east.stats.channel_a_errors);
353 printf(
" Ch-B errors: %u\n", voter->east.stats.channel_b_errors);
355 printf(
"\nActive Site: %s\n", voter->active_site == SITE_WEST ?
"WEST" :
"EAST");
356 printf(
"═══════════════════════════════════════════════════════════════\n");
370 fprintf(stderr,
"[MULTI-SITE] Shutting down voter\n");
371 voter->west.state = SITE_STATE_FAULTY;
372 voter->east.state = SITE_STATE_FAULTY;
int multi_site_get_service_output(multi_site_voter_t *voter, char *output_buf, uint32_t buf_size)
Get service output from active site (for printf, logging).
void multi_site_print_stats(const multi_site_voter_t *voter)
Print multi-site statistics for diagnostics.
int multi_site_voter_shutdown(multi_site_voter_t *voter)
Shutdown multi-site voter.
int multi_site_voter_init(multi_site_voter_t *voter, const multi_site_config_t *config)
Initialize multi-site voter with West online, East standby.
int multi_site_get_active_command(multi_site_voter_t *voter, train_command_t *cmd_out)
Get voted command from currently active site.
int multi_site_vote_cycle(multi_site_voter_t *voter, site_id_t site, const train_command_t *cmd_a, const train_command_t *cmd_b)
Process voting cycle on one site.
site_id_t multi_site_heartbeat(multi_site_voter_t *voter)
Check heartbeat between sites and trigger failover if needed.
int multi_site_failover_request(multi_site_voter_t *voter, site_id_t new_active)
Trigger manual failover (for maintenance).
Multi-Site 2oo2 Redundancy with Active/Standby Failover.