feat(v0.4): чистый ThermalPolicy (банды + гистерезис, A12/B10)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Alexander <akotenev2003@gmail.com>
This commit is contained in:
@@ -3,5 +3,6 @@
|
|||||||
|
|
||||||
pub mod fsm;
|
pub mod fsm;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
|
pub mod thermal;
|
||||||
|
|
||||||
pub use service::PowerService;
|
pub use service::PowerService;
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
//! Тепловая подсистема (A12/B10, спека v0.4 §5). `ThermalPolicy` — чистая (без I/O), с гистерезисом.
|
||||||
|
//! Источники/throttler/монитор — P8.5 (этот же файл).
|
||||||
|
|
||||||
|
/// Уровень теплового состояния. `Throttle(u8)` — банд (v0.4 использует уровень 1; мульти-банд — RK3588).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ThermalLevel {
|
||||||
|
Normal,
|
||||||
|
Warn,
|
||||||
|
Throttle(u8),
|
||||||
|
Critical,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThermalLevel {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ThermalLevel::Normal => "normal",
|
||||||
|
ThermalLevel::Warn => "warn",
|
||||||
|
ThermalLevel::Throttle(_) => "throttle",
|
||||||
|
ThermalLevel::Critical => "critical",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn rank(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
ThermalLevel::Normal => 0,
|
||||||
|
ThermalLevel::Warn => 1,
|
||||||
|
ThermalLevel::Throttle(_) => 2,
|
||||||
|
ThermalLevel::Critical => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пороги — placeholder-константы (°C). Тюнинг на RK3588 (hardware §1a; Tjmax ~100 °C).
|
||||||
|
pub const WARN_C: i32 = 75;
|
||||||
|
pub const THROTTLE_C: i32 = 85;
|
||||||
|
pub const CRITICAL_C: i32 = 95;
|
||||||
|
pub const HYST_C: i32 = 5;
|
||||||
|
|
||||||
|
/// Чистая политика: `(предыдущий уровень, температура) → уровень` с гистерезисом (Schmitt по бандам).
|
||||||
|
pub struct ThermalPolicy;
|
||||||
|
|
||||||
|
impl ThermalPolicy {
|
||||||
|
fn band_by_entry(t: i32) -> ThermalLevel {
|
||||||
|
if t >= CRITICAL_C {
|
||||||
|
ThermalLevel::Critical
|
||||||
|
} else if t >= THROTTLE_C {
|
||||||
|
ThermalLevel::Throttle(1)
|
||||||
|
} else if t >= WARN_C {
|
||||||
|
ThermalLevel::Warn
|
||||||
|
} else {
|
||||||
|
ThermalLevel::Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn band_by_exit(t: i32) -> ThermalLevel {
|
||||||
|
// нижние (гистерезисные) пороги = entry − HYST
|
||||||
|
if t >= CRITICAL_C - HYST_C {
|
||||||
|
ThermalLevel::Critical
|
||||||
|
} else if t >= THROTTLE_C - HYST_C {
|
||||||
|
ThermalLevel::Throttle(1)
|
||||||
|
} else if t >= WARN_C - HYST_C {
|
||||||
|
ThermalLevel::Warn
|
||||||
|
} else {
|
||||||
|
ThermalLevel::Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Подъём — по entry-порогам; спуск — по exit-порогам (entry − HYST) → нет осцилляции на границе.
|
||||||
|
pub fn next(prev: ThermalLevel, temp_c: i32) -> ThermalLevel {
|
||||||
|
let up = Self::band_by_entry(temp_c);
|
||||||
|
if up.rank() >= prev.rank() {
|
||||||
|
up
|
||||||
|
} else {
|
||||||
|
Self::band_by_exit(temp_c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod policy_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rises_by_entry_thresholds() {
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Normal, 70), ThermalLevel::Normal);
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Normal, 75), ThermalLevel::Warn);
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Warn, 85), ThermalLevel::Throttle(1));
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Throttle(1), 95), ThermalLevel::Critical);
|
||||||
|
// прыжок вверх через банды
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Normal, 99), ThermalLevel::Critical);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hysteresis_holds_until_below_exit() {
|
||||||
|
// critical держится до < 90 (95−5)
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Critical, 92), ThermalLevel::Critical);
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Critical, 89), ThermalLevel::Throttle(1));
|
||||||
|
// warn держится до < 70
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Warn, 73), ThermalLevel::Warn);
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Warn, 69), ThermalLevel::Normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_oscillation_at_boundary() {
|
||||||
|
// на 84 (чуть ниже entry throttle=85): зависит от prev (Schmitt), не дёргается
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Throttle(1), 84), ThermalLevel::Throttle(1));
|
||||||
|
assert_eq!(ThermalPolicy::next(ThermalLevel::Warn, 84), ThermalLevel::Warn);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user