gardesk/gartop / f1d25b2

Browse files

Add temperature collector (reads /sys/class/hwmon)

Authored by espadonne
SHA
f1d25b2f9245d6b320032e1ab930286d8ce620ae
Parents
78c91f8
Tree
d0a4eb5

1 changed file

StatusFile+-
A gartop/src/collector/temperature.rs 118 0
gartop/src/collector/temperature.rsadded
@@ -0,0 +1,118 @@
1
+//! Temperature sensor collection from /sys/class/hwmon
2
+
3
+use crate::error::Result;
4
+use gartop_ipc::{TempSensor, TempStats};
5
+use std::fs;
6
+use std::path::Path;
7
+use std::time::{SystemTime, UNIX_EPOCH};
8
+
9
+/// Temperature sensor collector.
10
+pub struct TempCollector;
11
+
12
+impl TempCollector {
13
+    /// Create a new temperature collector.
14
+    pub fn new() -> Result<Self> {
15
+        Ok(Self)
16
+    }
17
+
18
+    /// Collect current temperature readings from all sensors.
19
+    pub fn collect(&mut self) -> Result<TempStats> {
20
+        let mut sensors = Vec::new();
21
+        let hwmon_path = Path::new("/sys/class/hwmon");
22
+
23
+        if hwmon_path.exists() {
24
+            if let Ok(entries) = fs::read_dir(hwmon_path) {
25
+                for entry in entries.flatten() {
26
+                    let hwmon_dir = entry.path();
27
+                    if let Some(device_sensors) = Self::read_hwmon_device(&hwmon_dir) {
28
+                        sensors.extend(device_sensors);
29
+                    }
30
+                }
31
+            }
32
+        }
33
+
34
+        let timestamp = SystemTime::now()
35
+            .duration_since(UNIX_EPOCH)
36
+            .map(|d| d.as_millis() as u64)
37
+            .unwrap_or(0);
38
+
39
+        Ok(TempStats { sensors, timestamp })
40
+    }
41
+
42
+    /// Read all temperature sensors from a hwmon device directory.
43
+    fn read_hwmon_device(hwmon_dir: &Path) -> Option<Vec<TempSensor>> {
44
+        let device_name = Self::read_file(hwmon_dir.join("name")).unwrap_or_default();
45
+        let mut sensors = Vec::new();
46
+
47
+        // Find all temp*_input files
48
+        if let Ok(entries) = fs::read_dir(hwmon_dir) {
49
+            for entry in entries.flatten() {
50
+                let filename = entry.file_name();
51
+                let filename_str = filename.to_string_lossy();
52
+
53
+                if filename_str.starts_with("temp") && filename_str.ends_with("_input") {
54
+                    // Extract sensor index (e.g., "temp1_input" -> "1")
55
+                    let index = filename_str
56
+                        .strip_prefix("temp")
57
+                        .and_then(|s| s.strip_suffix("_input"))
58
+                        .unwrap_or("");
59
+
60
+                    if let Some(sensor) = Self::read_sensor(hwmon_dir, index, &device_name) {
61
+                        sensors.push(sensor);
62
+                    }
63
+                }
64
+            }
65
+        }
66
+
67
+        if sensors.is_empty() {
68
+            None
69
+        } else {
70
+            Some(sensors)
71
+        }
72
+    }
73
+
74
+    /// Read a single temperature sensor.
75
+    fn read_sensor(hwmon_dir: &Path, index: &str, device_name: &str) -> Option<TempSensor> {
76
+        // Read temperature (millidegrees Celsius)
77
+        let temp_path = hwmon_dir.join(format!("temp{}_input", index));
78
+        let temp_millidegrees: i64 = Self::read_file(&temp_path)?.trim().parse().ok()?;
79
+        let temp_celsius = temp_millidegrees as f64 / 1000.0;
80
+
81
+        // Read label (optional)
82
+        let label_path = hwmon_dir.join(format!("temp{}_label", index));
83
+        let label = Self::read_file(label_path)
84
+            .map(|s| s.trim().to_string())
85
+            .unwrap_or_else(|| format!("temp{}", index));
86
+
87
+        // Read critical threshold (optional, millidegrees)
88
+        let crit_path = hwmon_dir.join(format!("temp{}_crit", index));
89
+        let critical = Self::read_file(crit_path)
90
+            .and_then(|s| s.trim().parse::<i64>().ok())
91
+            .map(|m| m as f64 / 1000.0);
92
+
93
+        // Read high threshold (optional, millidegrees)
94
+        let high_path = hwmon_dir.join(format!("temp{}_max", index));
95
+        let high = Self::read_file(high_path)
96
+            .and_then(|s| s.trim().parse::<i64>().ok())
97
+            .map(|m| m as f64 / 1000.0);
98
+
99
+        Some(TempSensor {
100
+            label,
101
+            device: device_name.to_string(),
102
+            temp_celsius,
103
+            critical,
104
+            high,
105
+        })
106
+    }
107
+
108
+    /// Read a file to string, returning None on error.
109
+    fn read_file<P: AsRef<Path>>(path: P) -> Option<String> {
110
+        fs::read_to_string(path).ok()
111
+    }
112
+}
113
+
114
+impl Default for TempCollector {
115
+    fn default() -> Self {
116
+        Self
117
+    }
118
+}