feat(v0.4): TempSource/Throttler-абстракции + ThermalMonitor (A12/B10)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Alexander <akotenev2003@gmail.com>
This commit is contained in:
2026-06-25 15:32:53 +03:00
parent 860a591f16
commit 2e6144c54f
+150
View File
@@ -34,6 +34,7 @@ pub const WARN_C: i32 = 75;
pub const THROTTLE_C: i32 = 85;
pub const CRITICAL_C: i32 = 95;
pub const HYST_C: i32 = 5;
pub const POLL_SECS: u64 = 1; // период опроса температуры (монотоника)
/// Чистая политика: `(предыдущий уровень, температура) → уровень` с гистерезисом (Schmitt по бандам).
pub struct ThermalPolicy;
@@ -74,6 +75,155 @@ impl ThermalPolicy {
}
}
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Arc;
/// Источник температуры (°C). real = sysfs; VM = mock.
pub trait TempSource: Send + Sync {
fn read_celsius(&self) -> i32;
}
/// Mock-источник (dev): температуру задаёт `SetTemp` через dev-D-Bus.
#[derive(Clone)]
pub struct MockTempSource {
temp: Arc<AtomicI32>,
}
impl MockTempSource {
pub fn new(init_c: i32) -> Self {
Self {
temp: Arc::new(AtomicI32::new(init_c)),
}
}
pub fn set(&self, c: i32) {
self.temp.store(c, Ordering::Relaxed);
}
}
impl TempSource for MockTempSource {
fn read_celsius(&self) -> i32 {
self.temp.load(Ordering::Relaxed)
}
}
/// Прод: max по `/sys/class/thermal/thermal_zone*/temp` (миллиградусы). В Lima зоны статичны → числа на RK3588.
#[derive(Default)]
pub struct SysfsTempSource;
impl SysfsTempSource {
pub fn new() -> Self {
Self
}
}
impl TempSource for SysfsTempSource {
fn read_celsius(&self) -> i32 {
let mut max = i32::MIN;
if let Ok(rd) = std::fs::read_dir("/sys/class/thermal") {
for e in rd.flatten() {
let p = e.path().join("temp");
if let Ok(s) = std::fs::read_to_string(&p) {
if let Ok(milli) = s.trim().parse::<i32>() {
max = max.max(milli / 1000);
}
}
}
}
if max == i32::MIN {
0
} else {
max
}
}
}
/// Применение throttle. real = cpufreq (HW); VM = запись уровня (no-op-эффект).
pub trait Throttler: Send + Sync {
fn apply(&self, level: ThermalLevel);
}
/// VM/прод-каркас: логирует + запоминает последний уровень (реальный cpufreq — HW).
#[derive(Default, Clone)]
pub struct NoopThrottler {
last: Arc<std::sync::Mutex<Option<ThermalLevel>>>,
}
impl NoopThrottler {
pub fn last(&self) -> Option<ThermalLevel> {
*self.last.lock().unwrap()
}
}
impl Throttler for NoopThrottler {
fn apply(&self, level: ThermalLevel) {
*self.last.lock().unwrap() = Some(level);
tracing::info!(
"thermal: throttle уровень {} (эффект cpufreq — HW)",
level.as_str()
);
}
}
/// Результат шага монитора: уровень + рёбра входа/выхода Critical (для FSM ThermalTrip/ThermalCleared).
#[derive(Debug, PartialEq, Eq)]
pub struct ThermalObservation {
pub level: ThermalLevel,
pub changed: bool,
pub entered_critical: bool,
pub left_critical: bool,
}
/// Монитор: хранит предыдущий уровень, применяет политику, размечает рёбра Critical.
pub struct ThermalMonitor {
prev: ThermalLevel,
}
impl Default for ThermalMonitor {
fn default() -> Self {
Self {
prev: ThermalLevel::Normal,
}
}
}
impl ThermalMonitor {
pub fn new() -> Self {
Self::default()
}
pub fn observe(&mut self, temp_c: i32) -> ThermalObservation {
let level = ThermalPolicy::next(self.prev, temp_c);
let changed = level != self.prev;
let entered_critical = changed && level == ThermalLevel::Critical;
let left_critical = changed && self.prev == ThermalLevel::Critical;
self.prev = level;
ThermalObservation {
level,
changed,
entered_critical,
left_critical,
}
}
}
#[cfg(test)]
mod monitor_tests {
use super::*;
#[test]
fn marks_critical_edges() {
let mut m = ThermalMonitor::new();
let o = m.observe(96);
assert!(o.entered_critical && o.changed && o.level == ThermalLevel::Critical);
let o = m.observe(96); // держится — рёбер нет
assert!(!o.changed && !o.entered_critical);
let o = m.observe(80); // < 90 → выход из critical
assert!(o.left_critical && o.level == ThermalLevel::Throttle(1));
}
#[test]
fn mock_source_and_noop_throttler() {
let src = MockTempSource::new(20);
assert_eq!(src.read_celsius(), 20);
src.set(88);
assert_eq!(src.read_celsius(), 88);
let th = NoopThrottler::default();
th.apply(ThermalLevel::Throttle(1));
assert_eq!(th.last(), Some(ThermalLevel::Throttle(1)));
}
}
#[cfg(test)]
mod policy_tests {
use super::*;