feat(v0.4): FSM ThermalCleared (abort thermal) + FailsafeCut (MCU cut)
+ Action::Cut и его хендлер в apply_event (нужен для компиляции крейта — P8.6 шаг 2 сделан здесь). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Alexander <akotenev2003@gmail.com>
This commit is contained in:
@@ -30,6 +30,8 @@ pub enum Event {
|
||||
UnderVoltage,
|
||||
ThermalTrip,
|
||||
GraceExpired,
|
||||
ThermalCleared, // тепло вернулось в норму до PONR → abort thermal-shutdown (гейт reason==Thermal)
|
||||
FailsafeCut, // MCU-авторитетный cut (зависший SoC / истёк hold-up) → off, необратимо
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -39,6 +41,7 @@ pub enum Action {
|
||||
AccChanged(bool),
|
||||
StartGrace,
|
||||
Commit,
|
||||
Cut, // MCU снял питание (fail-safe) — сервис логирует + переходит в off
|
||||
}
|
||||
|
||||
pub struct PowerFsm {
|
||||
@@ -149,6 +152,23 @@ impl PowerFsm {
|
||||
};
|
||||
vec![Action::Commit]
|
||||
}
|
||||
// тепло вернулось до PONR → abort (только thermal-shutdown; ACC-off/under-voltage — no-op)
|
||||
(
|
||||
ShuttingDown {
|
||||
phase: Abortable,
|
||||
reason: ShutdownReason::Thermal,
|
||||
},
|
||||
E::ThermalCleared,
|
||||
) => {
|
||||
self.state = Running;
|
||||
vec![Action::ShutdownAborted]
|
||||
}
|
||||
// MCU fail-safe cut → off из любого не-off (необратимо, MCU-авторитет)
|
||||
(Off, E::FailsafeCut) => vec![],
|
||||
(_, E::FailsafeCut) => {
|
||||
self.state = Off;
|
||||
vec![Action::Cut]
|
||||
}
|
||||
// committed/off/sleep/battery_cutoff + всё прочее — no-op (committed не abort-ится)
|
||||
_ => vec![],
|
||||
}
|
||||
@@ -264,4 +284,33 @@ mod tests {
|
||||
IgnitionState::Off
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thermal_cleared_aborts_only_thermal_abortable() {
|
||||
let mut f = PowerFsm::new(); // Running
|
||||
f.step(Event::ThermalTrip); // → ShuttingDown{Abortable, Thermal}
|
||||
assert_eq!(f.step(Event::ThermalCleared), vec![Action::ShutdownAborted]);
|
||||
assert_eq!(f.state(), State::Running);
|
||||
|
||||
// из ACC-off-shutdown ThermalCleared — no-op
|
||||
let mut g = PowerFsm::new();
|
||||
g.step(Event::AccOff);
|
||||
assert_eq!(g.step(Event::ThermalCleared), vec![]);
|
||||
assert_eq!(g.power_state(), PowerState::ShuttingDown);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failsafe_cut_forces_off_from_any_nonoff() {
|
||||
let mut f = PowerFsm::new(); // Running
|
||||
assert_eq!(f.step(Event::FailsafeCut), vec![Action::Cut]);
|
||||
assert_eq!(f.state(), State::Off);
|
||||
// из off — no-op
|
||||
assert_eq!(f.step(Event::FailsafeCut), vec![]);
|
||||
// даже из committed (необратимый shutdown) cut уводит в off
|
||||
let mut g = PowerFsm::new();
|
||||
g.step(Event::AccOff);
|
||||
g.step(Event::GraceExpired); // committed
|
||||
assert_eq!(g.step(Event::FailsafeCut), vec![Action::Cut]);
|
||||
assert_eq!(g.state(), State::Off);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,9 @@ async fn apply_event(
|
||||
});
|
||||
}
|
||||
Action::Commit => durable_barrier(),
|
||||
Action::Cut => {
|
||||
tracing::warn!("power: MCU fail-safe cut (SoC hang / hold-up budget) — forced off");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user