phytrace_sdk/models/domains/
thermal.rs

1//! Thermal Domain - Temperature monitoring and cooling.
2
3use serde::{Deserialize, Serialize};
4use validator::Validate;
5
6use crate::models::enums::{CoolingMode, ThermalState};
7
8/// Thermal domain containing temperature and cooling information.
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10pub struct ThermalDomain {
11    /// Overall thermal state
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub thermal_state: Option<ThermalState>,
14
15    /// Component temperatures
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub components: Option<Vec<ThermalComponent>>,
18
19    /// Ambient temperature (Celsius)
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub ambient_temp_c: Option<f64>,
22
23    /// Cooling system
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub cooling_system: Option<CoolingSystem>,
26
27    /// Heating system
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub heating_system: Option<HeatingSystem>,
30
31    /// Whether thermal throttling active
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub is_throttling: Option<bool>,
34}
35
36/// Thermal component.
37#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
38pub struct ThermalComponent {
39    /// Component name
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub name: Option<String>,
42
43    /// Component type
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub component_type: Option<String>,
46
47    /// Temperature (Celsius)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub temperature_c: Option<f64>,
50
51    /// Warning threshold (Celsius)
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub warning_threshold_c: Option<f64>,
54
55    /// Critical threshold (Celsius)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub critical_threshold_c: Option<f64>,
58
59    /// Thermal state
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub state: Option<ThermalState>,
62}
63
64/// Cooling system information.
65#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
66pub struct CoolingSystem {
67    /// Cooling mode
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub mode: Option<CoolingMode>,
70
71    /// Fan speeds (RPM or percentage)
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub fan_speeds: Option<Vec<FanInfo>>,
74
75    /// Coolant temperature (for liquid cooling)
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub coolant_temp_c: Option<f64>,
78
79    /// Coolant flow rate (L/min)
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub coolant_flow_lpm: Option<f64>,
82
83    /// System operational
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub is_operational: Option<bool>,
86}
87
88/// Fan information.
89#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
90pub struct FanInfo {
91    /// Fan ID/name
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub name: Option<String>,
94
95    /// Speed (RPM)
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub speed_rpm: Option<u32>,
98
99    /// Speed percentage
100    #[serde(skip_serializing_if = "Option::is_none")]
101    #[validate(range(min = 0.0, max = 100.0))]
102    pub speed_pct: Option<f64>,
103
104    /// Is operational
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub is_operational: Option<bool>,
107}
108
109/// Heating system information.
110#[derive(Debug, Clone, Default, Serialize, Deserialize)]
111pub struct HeatingSystem {
112    /// Heating active
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub is_active: Option<bool>,
115
116    /// Target temperature (Celsius)
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub target_temp_c: Option<f64>,
119
120    /// Heating power (Watts)
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub power_w: Option<f64>,
123}
124
125impl ThermalDomain {
126    /// Create with thermal state.
127    pub fn new(state: ThermalState) -> Self {
128        Self {
129            thermal_state: Some(state),
130            is_throttling: Some(state == ThermalState::High || state == ThermalState::Critical),
131            ..Default::default()
132        }
133    }
134
135    /// Add component.
136    pub fn with_component(mut self, component: ThermalComponent) -> Self {
137        let components = self.components.get_or_insert_with(Vec::new);
138        components.push(component);
139        self
140    }
141
142    /// Set ambient temperature.
143    pub fn with_ambient(mut self, temp_c: f64) -> Self {
144        self.ambient_temp_c = Some(temp_c);
145        self
146    }
147
148    /// Add cooling system.
149    pub fn with_cooling(mut self, cooling: CoolingSystem) -> Self {
150        self.cooling_system = Some(cooling);
151        self
152    }
153}
154
155impl ThermalComponent {
156    /// Create a component.
157    pub fn new(name: impl Into<String>, temp_c: f64) -> Self {
158        let state = if temp_c > 90.0 {
159            ThermalState::Critical
160        } else if temp_c > 75.0 {
161            ThermalState::High
162        } else if temp_c > 60.0 {
163            ThermalState::Elevated
164        } else {
165            ThermalState::Normal
166        };
167
168        Self {
169            name: Some(name.into()),
170            temperature_c: Some(temp_c),
171            state: Some(state),
172            ..Default::default()
173        }
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_thermal_component() {
183        let comp = ThermalComponent::new("CPU", 65.0);
184        assert_eq!(comp.state, Some(ThermalState::Elevated));
185    }
186
187    #[test]
188    fn test_thermal_domain() {
189        let thermal = ThermalDomain::new(ThermalState::Normal)
190            .with_component(ThermalComponent::new("battery", 35.0))
191            .with_ambient(25.0);
192
193        assert_eq!(thermal.components.unwrap().len(), 1);
194    }
195}