What is Safe-State?
Safe-State provides fail-safe transitions for safety-critical systems. When any component detects a critical condition, it can transition the system to one of three levels:
- DEGRADED: Reduced functionality but still operating
- SAFE: Critical functions disabled, fail-safe state active
- REBOOT: Controlled restart required
Key principle: If correctness cannot be guaranteed, enter fail-safe mode.
Safe-State Levels
The module defines three severity levels (in order of escalation):
DEGRADED (Level 0)
Reduced capability but system can continue operating. Example: Redundant channel failed, but primary still working. Handler CAN return to caller (system continues execution).
SAFE (Level 1)
Fail-safe state: critical functions disabled, system in safe configuration. Example: Both channels failed, both must be considered unavailable. Handler MUST NOT return; if it does, framework enters infinite loop.
REBOOT (Level 2)
Emergency restart required. Example: Critical error detected, recovery only via fresh start. Handler MUST NOT return; if it does, framework enters infinite loop.
Quick Start
1. Register Handler at Startup
const char *file,
int32_t line,
const char *message) {
printf("Safe-state [%d]: reason=%u at %s:%ld msg=%s\n",
level, reason, file, line, message);
activate_fallback_mode();
return;
}
close_all_connections();
flush_logs();
}
my_safe_state_handler);
my_safe_state_handler);
my_safe_state_handler);
rte_safestate_level_t
Safe-state severity levels (ADR-004 section 2.1).
rte_status_t rte_safestate_register_handler(rte_safestate_level_t level, rte_safestate_handler_t handler)
Registers the reaction handler for one safe-state level. This is how an integrator supplies their own...
uint16_t rte_safestate_reason_t
Diagnostic reason code accompanying a safe-state transition.
@ RTE_SAFESTATE_LEVEL_REBOOT
@ RTE_SAFESTATE_LEVEL_DEGRADED
@ RTE_SAFESTATE_LEVEL_SAFE
2. Use RTE_ASSERT for Checked Assertions
Replace standard assert() with RTE_ASSERT() - always active, even in production.
#define RTE_ASSERT(cond)
Checked assertion. Always active in every build configuration, including production (ADR-004 section ...
On failure: enters SAFE state with RTE_SAFESTATE_REASON_ASSERT_FAILED.
3. Trigger Safe-State Explicitly
if (vital_channel_status == CHANNEL_ERROR) {
CUSTOM_REASON_VITAL_CHANNEL_FAILED);
}
if (recovery_failed) {
}
#define RTE_SAFESTATE(level, reason)
Explicitly enters the given safe-state level with a reason code, capturing the call site automaticall...
#define RTE_REBOOT(reason)
Enters RTE_SAFESTATE_LEVEL_REBOOT with the given reason code. Equivalent to RTE_SAFESTATE(RTE_SAFESTA...
Practical Examples
Example 1: Redundant Channel Voting Failure
rte_status_t process_vital_command(
const vital_msg_t *msg) {
if (msg->checksum_mismatch) {
log_error("Vital message checksum failed");
CUSTOM_REASON_CHECKSUM_MISMATCH);
}
if (!msg->channel_a_valid || !msg->channel_b_valid) {
log_error("Channel unavailable: a=%d b=%d",
msg->channel_a_valid, msg->channel_b_valid);
CUSTOM_REASON_CHANNEL_UNAVAILABLE);
}
execute_command(msg);
}
rte_status_t
Common result/status codes.
@ RTE_STATUS_INVALID_PARAM
Example 2: Graceful Degradation
void check_system_health(void) {
rte_system_health_t health;
get_system_health(&health);
uint32_t failures = 0;
if (!health.primary_ok) failures++;
if (!health.backup_ok) failures++;
if (!health.tertiary_ok) failures++;
if (failures == 0) {
CUSTOM_REASON_RECOVERED);
} else if (failures == 1) {
log_warning("System degraded: %u failures", failures);
CUSTOM_REASON_REDUCED_REDUNDANCY);
} else if (failures >= 2) {
log_critical("System failed: %u failures", failures);
CUSTOM_REASON_INSUFFICIENT_REDUNDANCY);
}
}
Example 3: Assertion-Based Fault Detection
void process_train_command(const train_cmd_t *cmd) {
execute_train_command(cmd);
}
Common Patterns
Pattern 1: Health Monitoring Loop
Periodically check system health and transition accordingly:
void health_check_loop(void) {
while (1) {
rte_system_status_t status = get_system_status();
if (status.all_healthy) {
}
} else if (status.critical_fault) {
CUSTOM_REASON_CRITICAL_FAULT);
}
sleep_ms(100);
}
}
Pattern 2: Fail-Safe-by-Default
Assume unsafe unless proven otherwise:
proceed_with_operation();
}
return;
}
proceed_with_operation();
Application-Specific Reason Codes
Framework reserves reason codes 0-4095. Applications use 4096 and above:
#define CUSTOM_REASON_VITAL_CHANNEL_FAILED 4096
#define CUSTOM_REASON_CHECKSUM_MISMATCH 4097
#define CUSTOM_REASON_REDUCED_REDUNDANCY 4098
#define CUSTOM_REASON_INSUFFICIENT_REDUNDANCY 4099
#define CUSTOM_REASON_CRITICAL_FAULT 4100
#define CUSTOM_REASON_RECOVERY_IMPOSSIBLE 4101
#define CUSTOM_REASON_CHANNEL_UNAVAILABLE 4102
#define CUSTOM_REASON_RECOVERED 4103
Best Practices
- Register all three handlers at startup
- Even if not all levels used, register handlers for each
- Prevents undefined behavior if level is entered without handler
- Handlers should not block
- Keep handler execution time minimal
- Log events, close resources quickly, then exit
- Framework guarantees no further code execution after SAFE/REBOOT
- Use meaningful reason codes
- Not just UNSPECIFIED
- Helps with diagnostics and post-incident analysis
- Reserve ranges: framework 0-4095, application 4096+
- RTE_ASSERT for programmer errors
- Use for assumptions that should always be true
- Example: pointer not NULL, array index in range
- Failure = bug in code, must stop immediately
- RTE_SAFESTATE for runtime faults
- Use for conditions that can legitimately fail at runtime
- Example: communication failure, sensor error
- Failure = system state invalid, must enter safe mode
See Also